Skip to content

Null package policy variables cause API validation errors in Fleet #2979

@andrewkroh

Description

@andrewkroh

Summary

When testing the beyondtrust_pra integration in the elastic/integrations repo after Kibana 9.2.0 introduced URL validation for the url variable type, I encountered errors when running system tests with elastic-package. The error occurs because elastic-package sends null for variables without a default and are not set in the test policy, specifically for variables of type url. The same will also be true for the duration variable which has min_duration and max_duration validations, but it is not currently used in any packages.

Example error:

system test: common in beyondtrust_pra.access_session
could not add data stream config to policy: could not add package to policy; API status code = 400; response body = {"statusCode":400,"error":"Bad Request","message":"Package policy is invalid: inputs.cel.vars.proxy_url: [\"Invalid URL format\"]"}

Request body sent:

{
  "vars": {
    "proxy_url": {
      "value": null,
      "type": "url"
    },
    ...
  }
}

Fleet's validation considers a variable as "defined" once it appears in the request, even if its value is null. All defined variables undergo type validation, and null fails URL validation. This results in errors for non-required url variables that are unset.

Expected behavior

Variables without a default should be omitted from the API request if they are not set, rather than being included with a null value. This will prevent unnecessary validation errors for unset, non-required variables of type url.

Additional observations

  • When using the newer inputs object format in the Fleet API, default values for required variables are automatically added by the API service. However, elastic-package currently uses the deprecated inputs array format, which requires all default values to be included explicitly in the request.
  • If default values are omitted, the API rejects the request, even for required variables with defaults specified in the manifest.
  • Relevant code: elastic-package test runner source and Fleet validation logic.

Steps to reproduce

  1. Run elastic-package test system for beyondtrust_pra integration with Kibana 9.2.0+.
  2. Observe the API error when a non-required url variable is not set in the test policy.

Suggested fix

  • Update elastic-package to omit unset variables that do not have a default from the package policy request body.
  • Consider switching to the supported Fleet API request format (inputs object) to benefit from automatic default value inclusion which would simplify building the package policy request.
Patch
diff --git a/internal/packages/archetype/data_stream_inputs.go b/internal/packages/archetype/data_stream_inputs.go
index cbbdd884..848043e3 100644
--- a/internal/packages/archetype/data_stream_inputs.go
+++ b/internal/packages/archetype/data_stream_inputs.go
@@ -84,6 +84,7 @@ func unpackVars(output *[]packages.Variable, input []InputVariable) {
 		newVar.MaxDuration = inputVar.MaxDuration
 		newVar.Description = inputVar.Description
 		if inputVar.Default != nil {
+			newVar.Default = &packages.VarValue{}
 			newVar.Default.Unpack(inputVar.Default)
 		}
 		*output = append(*output, newVar)
diff --git a/internal/packages/packages.go b/internal/packages/packages.go
index 3d4e60e2..218a7ac5 100644
--- a/internal/packages/packages.go
+++ b/internal/packages/packages.go
@@ -102,19 +102,19 @@ func VarValueYamlString(vv VarValue, field string, numSpaces ...int) string {
 
 // Variable is an instance of configuration variable (named, typed).
 type Variable struct {
-	Name                  string   `config:"name" json:"name" yaml:"name"`
-	Type                  string   `config:"type" json:"type" yaml:"type"`
-	Title                 string   `config:"title" json:"title" yaml:"title"`
-	Description           string   `config:"description" json:"description" yaml:"description"`
-	Multi                 bool     `config:"multi" json:"multi" yaml:"multi"`
-	Required              bool     `config:"required" json:"required" yaml:"required"`
-	Secret                bool     `config:"secret" json:"secret" yaml:"secret"`
-	ShowUser              bool     `config:"show_user" json:"show_user" yaml:"show_user"`
-	HideInDeploymentModes []string `config:"hide_in_deployment_modes" json:"hide_in_deployment_modes" yaml:"hide_in_deployment_modes"`
-	UrlAllowedSchemes     []string `config:"url_allowed_schemes" json:"url_allowed_schemes" yaml:"url_allowed_schemes"`
-	MinDuration           string   `config:"min_duration" json:"min_duration" yaml:"min_duration"`
-	MaxDuration           string   `config:"max_duration" json:"max_duration" yaml:"max_duration"`
-	Default               VarValue `config:"default" json:"default" yaml:"default"`
+	Name                  string    `config:"name" json:"name" yaml:"name"`
+	Type                  string    `config:"type" json:"type" yaml:"type"`
+	Title                 string    `config:"title" json:"title" yaml:"title"`
+	Description           string    `config:"description" json:"description" yaml:"description"`
+	Multi                 bool      `config:"multi" json:"multi" yaml:"multi"`
+	Required              bool      `config:"required" json:"required" yaml:"required"`
+	Secret                bool      `config:"secret" json:"secret" yaml:"secret"`
+	ShowUser              bool      `config:"show_user" json:"show_user" yaml:"show_user"`
+	HideInDeploymentModes []string  `config:"hide_in_deployment_modes" json:"hide_in_deployment_modes" yaml:"hide_in_deployment_modes"`
+	UrlAllowedSchemes     []string  `config:"url_allowed_schemes" json:"url_allowed_schemes" yaml:"url_allowed_schemes"`
+	MinDuration           string    `config:"min_duration" json:"min_duration" yaml:"min_duration"`
+	MaxDuration           string    `config:"max_duration" json:"max_duration" yaml:"max_duration"`
+	Default               *VarValue `config:"default" json:"default" yaml:"default"`
 }
 
 // Input is a single input configuration.
diff --git a/internal/resources/fleetpolicy.go b/internal/resources/fleetpolicy.go
index 37b4e919..61ca1677 100644
--- a/internal/resources/fleetpolicy.go
+++ b/internal/resources/fleetpolicy.go
@@ -312,17 +312,24 @@ func createInputPackagePolicy(policy FleetAgentPolicy, manifest packages.Package
 func setKibanaVariables(definitions []packages.Variable, values common.MapStr) kibana.Vars {
 	vars := kibana.Vars{}
 	for _, definition := range definitions {
+		// Elastic Package uses the deprecated 'inputs' array in its /api/fleet/package_policies request.
+		// When using this API parameter, default values are not automatically incorporated into
+		// the policy, whereas with the 'inputs' object, defaults are incorporated by the API service.
+		// This means that our client must include the default values in its request to ensure correct behavior.
 		val := definition.Default
 
 		value, err := values.GetValue(definition.Name)
 		if err == nil {
-			val = packages.VarValue{}
+			val = &packages.VarValue{}
 			val.Unpack(value)
+		} else if errors.Is(err, common.ErrKeyNotFound) && definition.Default == nil {
+			// Do not include nulls for unset variables.
+			continue
 		}
 
 		vars[definition.Name] = kibana.Var{
 			Type:  definition.Type,
-			Value: val,
+			Value: *val,
 		}
 	}
 	return vars
diff --git a/internal/testrunner/runners/system/tester.go b/internal/testrunner/runners/system/tester.go
index 362cb486..21517acd 100644
--- a/internal/testrunner/runners/system/tester.go
+++ b/internal/testrunner/runners/system/tester.go
@@ -2042,17 +2042,24 @@ func createInputPackageDatastream(
 func setKibanaVariables(definitions []packages.Variable, values common.MapStr) kibana.Vars {
 	vars := kibana.Vars{}
 	for _, definition := range definitions {
+		// Elastic Package uses the deprecated 'inputs' array in its /api/fleet/package_policies request.
+		// When using this API parameter, default values are not automatically incorporated into
+		// the policy, whereas with the 'inputs' object, defaults are incorporated by the API service.
+		// This means that our client must include the default values in its request to ensure correct behavior.
 		val := definition.Default
 
 		value, err := values.GetValue(definition.Name)
 		if err == nil {
-			val = packages.VarValue{}
+			val = &packages.VarValue{}
 			val.Unpack(value)
+		} else if errors.Is(err, common.ErrKeyNotFound) && definition.Default == nil {
+			// Do not include nulls for unset variables.
+			continue
 		}
 
 		vars[definition.Name] = kibana.Var{
 			Type:  definition.Type,
-			Value: val,
+			Value: *val,
 		}
 	}
 	return vars

Impact

This bug prevents system tests from passing due to invalid API requests, particularly for integrations using non-required variables of type url. This affects:

  • beyondtrust_pra.access_session
  • ti_cif3.feed
  • ti_recordedfuture.threat

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions