Fix google calendar key validation (#44556)

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

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
- [X] gitops run with extra keys (besides `client_email` and
`private_key` in `api_key_json` fails on main, passes on this branch
- [X] gitops run with missing `client_email` or `private_key` in
`api_key_json` still fails gitops (including dry run)
- [X] gitops run with extra keys sibling to api_key_json still fails as
expected



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Corrected GitOps validation so Google Calendar API key JSON no longer
rejects valid nested keys; required-field validation for the integration
still enforced.
* **Tests**
* Added test coverage to ensure nested unknown keys are accepted while
sibling-level unknown fields are reported as validation errors.

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/44556)
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tim Lee <timlee@fleetdm.com>
This commit is contained in:
Scott Gress
2026-05-11 13:41:28 -05:00
committed by GitHub
co-authored by Copilot Autofix powered by AI Tim Lee
parent c6407f9d24
commit f5c59ae3b4
3 changed files with 26 additions and 24 deletions
@@ -0,0 +1 @@
- Fixed issue where GitOps would incorrectly reject keys in Google Calendar API key JSON
+20 -13
View File
@@ -129,16 +129,25 @@ func TestValidateUnknownKeys(t *testing.T) {
assert.Len(t, errs, 2)
})
t.Run("ValidKeysProvider accepts declared keys", func(t *testing.T) {
// GoogleCalendarApiKey implements ValidKeysProvider to declare accepted
// keys for its custom JSON marshaling.
t.Run("api_key_json keys are not validated", func(t *testing.T) {
// GoogleCalendarApiKey accepts the full Google service-account JSON blob,
// so unknown-key validation is intentionally skipped for its contents.
data := map[string]any{
"google_calendar": []any{
map[string]any{
"domain": "example.com",
"api_key_json": map[string]any{
"client_email": "test@example.com",
"private_key": "some value",
"client_email": "test@example.com",
"private_key": "some value",
"type": "service_account",
"project_id": "fleet-dogfood",
"private_key_id": "abc123",
"client_id": "1234567890",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/foo",
"universe_domain": "googleapis.com",
},
},
},
@@ -147,22 +156,20 @@ func TestValidateUnknownKeys(t *testing.T) {
assert.Empty(t, errs)
})
t.Run("ValidKeysProvider rejects undeclared keys", func(t *testing.T) {
t.Run("unknown keys at google_calendar entry level are rejected", func(t *testing.T) {
// Skipping validation for api_key_json must not leak into siblings.
data := map[string]any{
"google_calendar": []any{
map[string]any{
"domain": "example.com",
"api_key_json": map[string]any{
"client_email": "test@example.com",
"private_key": "nothing to see here",
"bad_field": "unknown",
},
"domain": "example.com",
"api_key_json": map[string]any{"client_email": "x", "private_key": "y"},
"bad_sibling": true,
},
},
}
errs := validateUnknownKeys(data, reflect.TypeFor[fleet.Integrations](), []string{"org_settings", "integrations"}, "test.yml")
require.Len(t, errs, 1)
assert.Contains(t, errs[0].Error(), "bad_field")
assert.Contains(t, errs[0].Error(), "bad_sibling")
})
t.Run("scalar data no errors", func(t *testing.T) {
+5 -11
View File
@@ -389,14 +389,15 @@ const (
GoogleCalendarPrivateKey = "private_key"
)
// googleCalendarKeyNames lists all valid JSON keys for GoogleCalendarApiKey,
// used by ValidKeys() for gitops unknown-key validation.
var googleCalendarKeyNames = []string{GoogleCalendarEmail, GoogleCalendarPrivateKey}
// GoogleCalendarApiKey is a custom type for the Google Calendar API key JSON.
// It handles JSON marshaling/unmarshaling with support for masking sensitive data.
// When marshaled in masked state, it serializes to just "********".
// When unmarshaled, it accepts either "********" (indicating masked/preserve) or a JSON object.
//
// GitOps unknown-key validation is intentionally skipped for this type: users
// typically paste the full Google service-account JSON blob (which contains many
// keys beyond the two Fleet uses), and ValidateGoogleCalendarIntegrations still
// enforces that client_email and private_key are present.
type GoogleCalendarApiKey struct {
// Values contains the actual API key fields when not masked
Values map[string]string
@@ -404,13 +405,6 @@ type GoogleCalendarApiKey struct {
masked bool
}
// ValidKeys returns the set of accepted JSON keys for this type.
// This is used by gitops validation to check for unknown keys in types
// with custom JSON marshaling.
func (GoogleCalendarApiKey) ValidKeys() []string {
return googleCalendarKeyNames
}
// MarshalJSON implements json.Marshaler. When masked, returns "********".
// Otherwise, returns the JSON object representation of the values.
func (k GoogleCalendarApiKey) MarshalJSON() ([]byte, error) {