Skip to content

Commit

Permalink
fix(ssi): update mutating webhooks to use their own config
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This commit updates the config webhook and the tags to
labels webhook to use their own configs when constructing their
injection filter. While there were no substantial changes to the tests,
this change could certainly have subtle consequences.
  • Loading branch information
betterengineering committed Jan 30, 2025
1 parent 7b4bcc5 commit d2f8f08
Showing 10 changed files with 54 additions and 89 deletions.
17 changes: 3 additions & 14 deletions pkg/clusteragent/admission/controllers/webhook/controller_base.go
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@ import (
agentsidecar "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/agent_sidecar"
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation"
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoscaling"
mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common"
configWebhook "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/config"
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/cwsinstrumentation"
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/tagsfromlabels"
@@ -99,16 +98,6 @@ type Webhook interface {
// doesn't always work on Fargate (one of the envs where we use an agent sidecar), and
// the agent sidecar webhook needs to remove it.
func (c *controllerBase) generateWebhooks(wmeta workloadmeta.Component, pa workload.PodPatcher, datadogConfig config.Component, demultiplexer demultiplexer.Component) []Webhook {
// Note: the auto_instrumentation pod injection filter is used across
// multiple mutating webhooks, so we add it as a hard dependency to each
// of the components that use it via the injectionFilter parameter.
// TODO: for now we ignore the error returned by NewInjectionFilter, but we should not and surface it
// in the admission controller section in agent status.
enabled := datadogConfig.GetBool("apm_config.instrumentation.enabled")
enabledNamespaces := datadogConfig.GetStringSlice("apm_config.instrumentation.enabled_namespaces")
disabledNamespaces := datadogConfig.GetStringSlice("apm_config.instrumentation.disabled_namespaces")
injectionFilter, _ := mutatecommon.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)

var webhooks []Webhook
var validatingWebhooks []Webhook
var mutatingWebhooks []Webhook
@@ -125,15 +114,15 @@ func (c *controllerBase) generateWebhooks(wmeta workloadmeta.Component, pa workl
// Add Mutating webhooks.
if c.config.isMutationEnabled() {
mutatingWebhooks = []Webhook{
configWebhook.NewWebhook(wmeta, injectionFilter, datadogConfig),
tagsfromlabels.NewWebhook(wmeta, datadogConfig, injectionFilter),
configWebhook.NewWebhook(wmeta, datadogConfig),
tagsfromlabels.NewWebhook(wmeta, datadogConfig),
agentsidecar.NewWebhook(datadogConfig),
autoscaling.NewWebhook(pa, datadogConfig),
}
webhooks = append(webhooks, mutatingWebhooks...)

// APM Instrumentation webhook needs to be registered after the configWebhook webhook.
apm, err := autoinstrumentation.NewWebhook(wmeta, datadogConfig, injectionFilter)
apm, err := autoinstrumentation.NewWebhook(wmeta, datadogConfig)
if err == nil {
webhooks = append(webhooks, apm)
} else {
Original file line number Diff line number Diff line change
@@ -51,7 +51,8 @@ type Webhook struct {
operations []admissionregistrationv1.OperationType
matchConditions []admissionregistrationv1.MatchCondition

wmeta workloadmeta.Component
wmeta workloadmeta.Component
injectionFilter mutatecommon.InjectionFilter

// use to store all the config option from the config component to avoid costly lookups in the admission webhook hot path.
config webhookConfig
@@ -62,19 +63,19 @@ type Webhook struct {
}

// NewWebhook returns a new Webhook dependent on the injection filter.
func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component, filter mutatecommon.InjectionFilter) (*Webhook, error) {
// Note: the webhook is not functional with the filter being disabled--
// and the filter is _global_! so we need to make sure that it was
// initialized as it validates the configuration itself.
if filter == nil {
return nil, errors.New("filter required for auto_instrumentation webhook")
} else if err := filter.InitError(); err != nil {
return nil, fmt.Errorf("filter error: %w", err)
func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component) (*Webhook, error) {
config, err := retrieveConfig(datadogConfig)
if err != nil {
return nil, fmt.Errorf("unable to retrieve autoinstrumentation config, err: %w", err)
}

config, err := retrieveConfig(datadogConfig, filter)
injectionFilter, err := mutatecommon.NewInjectionFilter(
config.InstrumentationConfig.Enabled,
config.InstrumentationConfig.EnabledNamespaces,
config.InstrumentationConfig.DisabledNamespaces,
)
if err != nil {
return nil, fmt.Errorf("unable to retrieve autoinstrumentation config, err: %w", err)
return nil, fmt.Errorf("error creating injection filter: %w", err)
}

webhook := &Webhook{
@@ -83,6 +84,7 @@ func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component, fi
resources: map[string][]string{"": {"pods"}},
operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
matchConditions: []admissionregistrationv1.MatchCondition{},
injectionFilter: injectionFilter,
wmeta: wmeta,

config: config,
@@ -147,7 +149,7 @@ func (w *Webhook) WebhookFunc() admission.WebhookFunc {

// isPodEligible checks whether we are allowed to inject in this pod.
func (w *Webhook) isPodEligible(pod *corev1.Pod) bool {
return w.config.injectionFilter.ShouldMutatePod(pod)
return w.injectionFilter.ShouldMutatePod(pod)
}

func (w *Webhook) inject(pod *corev1.Pod, ns string, _ dynamic.Interface) (bool, error) {
@@ -408,7 +410,7 @@ func (w *Webhook) initExtractedLibInfo(pod *corev1.Pod) extractedPodLibInfo {
languageDetection *libInfoLanguageDetection
)

if w.config.injectionFilter.IsNamespaceEligible(pod.Namespace) {
if w.injectionFilter.IsNamespaceEligible(pod.Namespace) {
source = libInfoSourceSingleStepInstrumentation
languageDetection = w.getLibrariesLanguageDetection(pod)
}
@@ -751,7 +753,7 @@ func (w *Webhook) injectAutoInstruConfig(pod *corev1.Pod, config extractedPodLib
log.Errorf("Cannot inject library configuration into pod %s: %s", mutatecommon.PodString(pod), err)
}

if w.config.injectionFilter.IsNamespaceEligible(pod.Namespace) {
if w.injectionFilter.IsNamespaceEligible(pod.Namespace) {
_ = basicLibConfigInjector{}.mutatePod(pod)
}

Original file line number Diff line number Diff line change
@@ -1657,11 +1657,7 @@ func TestInjectLibInitContainer(t *testing.T) {
if tt.mem != "" {
conf.SetWithoutSource("admission_controller.auto_instrumentation.init_resources.memory", tt.mem)
}
enabled := conf.GetBool("apm_config.instrumentation.enabled")
enabledNamespaces := conf.GetStringSlice("apm_config.instrumentation.enabled_namespaces")
disabledNamespaces := conf.GetStringSlice("apm_config.instrumentation.disabled_namespaces")
filter, _ := common.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)
wh, err := NewWebhook(wmeta, conf, filter)
wh, err := NewWebhook(wmeta, conf)
if (err != nil) != tt.wantErr {
t.Errorf("injectLibInitContainer() error = %v, wantErr %v", err, tt.wantErr)
}
@@ -3330,11 +3326,7 @@ func TestInjectAutoInstrumentation(t *testing.T) {
}
}

enabled := mockConfig.GetBool("apm_config.instrumentation.enabled")
enabledNamespaces := mockConfig.GetStringSlice("apm_config.instrumentation.enabled_namespaces")
disabledNamespaces := mockConfig.GetStringSlice("apm_config.instrumentation.disabled_namespaces")
filter, _ := common.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)
webhook, errInitAPMInstrumentation := NewWebhook(wmeta, mockConfig, filter)
webhook, errInitAPMInstrumentation := NewWebhook(wmeta, mockConfig)
if tt.wantWebhookInitErr {
require.Error(t, errInitAPMInstrumentation)
return
@@ -3597,11 +3589,7 @@ func TestShouldInject(t *testing.T) {
}

func mustWebhook(t *testing.T, wmeta workloadmeta.Component, ddConfig config.Component) *Webhook {
enabled := ddConfig.GetBool("apm_config.instrumentation.enabled")
enabledNamespaces := ddConfig.GetStringSlice("apm_config.instrumentation.enabled_namespaces")
disabledNamespaces := ddConfig.GetStringSlice("apm_config.instrumentation.disabled_namespaces")
filter, _ := common.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)
webhook, err := NewWebhook(wmeta, ddConfig, filter)
webhook, err := NewWebhook(wmeta, ddConfig)
require.NoError(t, err)
return webhook
}
14 changes: 7 additions & 7 deletions pkg/clusteragent/admission/mutate/autoinstrumentation/config.go
Original file line number Diff line number Diff line change
@@ -82,15 +82,15 @@ type webhookConfig struct {
// keep pointers to bool to differentiate between unset and false
// for backward compatibility with the previous implementation.
// TODO: remove the pointers when the backward compatibility is not needed anymore.
asmEnabled *bool
iastEnabled *bool
asmScaEnabled *bool
profilingEnabled *string
asmEnabled *bool
iastEnabled *bool
asmScaEnabled *bool
profilingEnabled *string
InstrumentationConfig *InstrumentationConfig

// configuration for the libraries init-containers to inject.
containerRegistry string
injectorImageTag string
injectionFilter mutatecommon.InjectionFilter
pinnedLibraries []libInfo
initSecurityContext *corev1.SecurityContext
defaultResourceRequirements initResourceRequirementConfiguration
@@ -99,7 +99,7 @@ type webhookConfig struct {
type initResourceRequirementConfiguration map[corev1.ResourceName]resource.Quantity

// retrieveConfig retrieves the configuration for the autoinstrumentation webhook from the datadog config
func retrieveConfig(datadogConfig config.Component, injectionFilter mutatecommon.InjectionFilter) (webhookConfig, error) {
func retrieveConfig(datadogConfig config.Component) (webhookConfig, error) {
webhookConfig := webhookConfig{
isEnabled: datadogConfig.GetBool("admission_controller.auto_instrumentation.enabled"),
endpoint: datadogConfig.GetString("admission_controller.auto_instrumentation.endpoint"),
@@ -114,14 +114,14 @@ func retrieveConfig(datadogConfig config.Component, injectionFilter mutatecommon
profilingEnabled: getOptionalStringValue(datadogConfig, "admission_controller.auto_instrumentation.profiling.enabled"),

containerRegistry: mutatecommon.ContainerRegistry(datadogConfig, "admission_controller.auto_instrumentation.container_registry"),
injectionFilter: injectionFilter,
}

instCfg, err := NewInstrumentationConfig(datadogConfig)
if err != nil {
return webhookConfig, err
}

webhookConfig.InstrumentationConfig = instCfg
webhookConfig.pinnedLibraries = getPinnedLibraries(instCfg.LibVersions, webhookConfig.containerRegistry)
webhookConfig.injectorImageTag = instCfg.InjectorImageTag

7 changes: 1 addition & 6 deletions pkg/clusteragent/admission/mutate/common/injection_filter.go
Original file line number Diff line number Diff line change
@@ -13,15 +13,10 @@ import (
"github.com/DataDog/datadog-agent/pkg/util/log"
)

// NewInjectionFilter constructs an injection filter using the autoinstrumentation
// GetNamespaceInjectionFilter.
// NewInjectionFilter constructs an injection filter.
func NewInjectionFilter(enabled bool, enabledNamespaces []string, disabledNamespaces []string) (InjectionFilter, error) {

// datadogConfig.GetStringSlice("apm_config.instrumentation.enabled_namespaces"),
// datadogConfig.GetStringSlice("apm_config.instrumentation.disabled_namespaces"),
filter, err := makeAPMSSINamespaceFilter(enabledNamespaces, disabledNamespaces)

// datadogConfig.GetBool("apm_config.instrumentation.enabled")
injectionFilter := &injectionFilter{
apmInstrumentationEnabled: enabled,
filter: filter,
Original file line number Diff line number Diff line change
@@ -26,14 +26,6 @@ type InjectionFilter interface {
InitError() error
}

// NewInjectionFilter creates a new InjectionFilter with the given NamespaceInjectionFilter.
// the InjectionFilter encapsulates the logic for deciding whether
// we can do pod mutation based on a NSFilter (NamespaceInjectionFilter).
// See: InjectionFilter.ShouldMutatePod.
// func NewInjectionFilter(filter NamespaceInjectionFilter) InjectionFilter {
// return &injectionFilterImpl{NSFilter: filter}
// }

type injectionFilterImpl struct {
NSFilter NamespaceInjectionFilter
}
9 changes: 7 additions & 2 deletions pkg/clusteragent/admission/mutate/config/config.go
Original file line number Diff line number Diff line change
@@ -86,10 +86,15 @@ type Webhook struct {
}

// NewWebhook returns a new Webhook
func NewWebhook(wmeta workloadmeta.Component, injectionFilter mutatecommon.InjectionFilter, datadogConfig config.Component) *Webhook {
func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component) *Webhook {
enabled := datadogConfig.GetBool("admission_controller.inject_config.enabled")
enabledNamespaces := datadogConfig.GetStringSlice("admission_controller.inject_config.enabled_namespaces")
disabledNamespaces := datadogConfig.GetStringSlice("admission_controller.inject_config.disabled_namespaces")
injectionFilter, _ := mutatecommon.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)

return &Webhook{
name: webhookName,
isEnabled: datadogConfig.GetBool("admission_controller.inject_config.enabled"),
isEnabled: enabled,
endpoint: datadogConfig.GetString("admission_controller.inject_config.endpoint"),
resources: map[string][]string{"": {"pods"}},
operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
24 changes: 8 additions & 16 deletions pkg/clusteragent/admission/mutate/config/config_test.go
Original file line number Diff line number Diff line change
@@ -78,8 +78,7 @@ func TestInjectHostIP(t *testing.T) {
pod = mutatecommon.WithLabels(pod, map[string]string{"admission.datadoghq.com/enabled": "true"})
wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams()))
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.Nil(t, err)
assert.True(t, injected)
@@ -91,8 +90,7 @@ func TestInjectService(t *testing.T) {
pod = mutatecommon.WithLabels(pod, map[string]string{"admission.datadoghq.com/enabled": "true", "admission.datadoghq.com/config.mode": "service"})
wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams()))
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.Nil(t, err)
assert.True(t, injected)
@@ -122,8 +120,7 @@ func TestInjectEntityID(t *testing.T) {
fx.Replace(config.MockParams{Overrides: tt.configOverrides}),
)
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.Nil(t, err)
assert.True(t, injected)
@@ -311,8 +308,7 @@ func TestInjectSocket(t *testing.T) {
pod = mutatecommon.WithLabels(pod, map[string]string{"admission.datadoghq.com/enabled": "true", "admission.datadoghq.com/config.mode": "socket"})
wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams()))
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.Nil(t, err)
assert.True(t, injected)
@@ -340,8 +336,7 @@ func TestInjectSocket_VolumeTypeSocket(t *testing.T) {
}),
)
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.Nil(t, err)
assert.True(t, injected)
@@ -437,8 +432,7 @@ func TestInjectSocketWithConflictingVolumeAndInitContainer(t *testing.T) {

wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams()))
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
injected, err := webhook.inject(pod, "", nil)
assert.True(t, injected)
assert.Nil(t, err)
@@ -478,8 +472,7 @@ func TestJSONPatchCorrectness(t *testing.T) {
fx.Replace(config.MockParams{Overrides: tt.overrides}),
)
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
request := admission.Request{
Object: podJSON,
Namespace: "bar",
@@ -510,8 +503,7 @@ func BenchmarkJSONPatch(b *testing.B) {

wmeta := fxutil.Test[workloadmeta.Component](b, core.MockBundle())
datadogConfig := fxutil.Test[config.Component](b, core.MockBundle())
filter, _ := mutatecommon.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, filter, datadogConfig)
webhook := NewWebhook(wmeta, datadogConfig)
podJSON := obj.(*admiv1.AdmissionReview).Request.Object.Raw

b.ResetTimer()
10 changes: 7 additions & 3 deletions pkg/clusteragent/admission/mutate/tagsfromlabels/tags.go
Original file line number Diff line number Diff line change
@@ -56,10 +56,15 @@ type Webhook struct {
}

// NewWebhook returns a new Webhook
func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component, injectionFilter mutatecommon.InjectionFilter) *Webhook {
func NewWebhook(wmeta workloadmeta.Component, datadogConfig config.Component) *Webhook {
enabled := datadogConfig.GetBool("admission_controller.inject_tags.enabled.enabled")
enabledNamespaces := datadogConfig.GetStringSlice("admission_controller.inject_tags.enabled_namespaces")
disabledNamespaces := datadogConfig.GetStringSlice("admission_controller.inject_tags.disabled_namespaces")
injectionFilter, _ := mutatecommon.NewInjectionFilter(enabled, enabledNamespaces, disabledNamespaces)

return &Webhook{
name: webhookName,
isEnabled: datadogConfig.GetBool("admission_controller.inject_tags.enabled"),
isEnabled: enabled,
endpoint: datadogConfig.GetString("admission_controller.inject_tags.endpoint"),
resources: map[string][]string{"": {"pods"}},
operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
@@ -154,7 +159,6 @@ func (w *Webhook) injectTags(pod *corev1.Pod, ns string, dc dynamic.Interface) (

if !w.injectionFilter.ShouldMutatePod(pod) {
// Ignore pod if it has the label admission.datadoghq.com/enabled=false
// or Single step configuration is disabled
return false, nil
}

6 changes: 2 additions & 4 deletions pkg/clusteragent/admission/mutate/tagsfromlabels/tags_test.go
Original file line number Diff line number Diff line change
@@ -174,8 +174,7 @@ func Test_injectTags(t *testing.T) {
datadogConfig := fxutil.Test[config.Component](t, core.MockBundle())
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter, _ := common.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, datadogConfig, filter)
webhook := NewWebhook(wmeta, datadogConfig)
_, err := webhook.injectTags(tt.pod, "ns", nil)
assert.NoError(t, err)
assert.Len(t, tt.pod.Spec.Containers, 1)
@@ -276,8 +275,7 @@ func TestGetAndCacheOwner(t *testing.T) {
owner := newOwner(kubeObj)
wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams()))
config := fxutil.Test[config.Component](t, core.MockBundle())
filter, _ := common.NewInjectionFilter(false, nil, nil)
webhook := NewWebhook(wmeta, config, filter)
webhook := NewWebhook(wmeta, config)

// Cache hit
cache.Cache.Set(ownerInfo.buildID(testNamespace), owner, webhook.ownerCacheTTL)

0 comments on commit d2f8f08

Please sign in to comment.