AP: Validate top-level keys in android profile upload (#34360)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #34334 



# Checklist for submitter

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually
This commit is contained in:
Magnus Jensen 2025-10-16 14:06:05 -03:00 committed by GitHub
parent 92a58851fa
commit 4609bca8cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 0 deletions

View file

@ -6,6 +6,9 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/fleetdm/fleet/v4/server/mdm"
@ -71,6 +74,10 @@ func (m *MDMAndroidConfigProfile) ValidateUserProvided() error {
if errMsg, ok := AndroidForbiddenJSONKeys[key]; ok {
return errors.New(errMsg)
}
if !IsAndroidPolicyFieldValid(key) {
return fmt.Errorf("Invalid JSON payload. Unknown key %q", key)
}
}
return nil
@ -134,3 +141,34 @@ type AndroidPolicyRequestPayload struct {
type AndroidPolicyRequestPayloadMetadata struct {
SettingsOrigin map[string]string `json:"settings_origin"` // Map of policy setting name, to profile uuid.
}
var (
policyFieldsCache map[string]bool
policyFieldsOnce sync.Once
)
// Initialize the cache once, lazily, with only JSON tag names.
// Since we take in the JSON value.
func initPolicyFieldsCache() {
policyFieldsCache = make(map[string]bool)
policyType := reflect.TypeOf(androidmanagement.Policy{})
for i := 0; i < policyType.NumField(); i++ {
field := policyType.Field(i)
// Add JSON tag name if it exists
jsonTag := field.Tag.Get("json")
if jsonTag != "" {
tagName := strings.Split(jsonTag, ",")[0]
if tagName != "" && tagName != "-" {
policyFieldsCache[tagName] = true
}
}
}
}
// Fast lookup using cached field names
func IsAndroidPolicyFieldValid(fieldName string) bool {
policyFieldsOnce.Do(initPolicyFieldsCache)
return policyFieldsCache[fieldName]
}

View file

@ -0,0 +1,15 @@
package fleet
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIsAndroidPolicyFieldValid(t *testing.T) {
isValid := IsAndroidPolicyFieldValid("bogusKeyThatWillNeverExist")
require.False(t, isValid)
isValid = IsAndroidPolicyFieldValid("name") // "name" is a valid top-level policy field, that we assume will exist forever
require.True(t, isValid)
}