diff --git a/internal/server/receiver_handler_test.go b/internal/server/receiver_handler_test.go index f425f0a6b..ee8c9709a 100644 --- a/internal/server/receiver_handler_test.go +++ b/internal/server/receiver_handler_test.go @@ -1233,14 +1233,115 @@ func Test_handlePayload(t *testing.T) { Name: "gcr-token", }, Data: map[string][]byte{ - "token": []byte("token"), - "email": []byte("test@example.iam.gserviceaccount.com"), + "token": []byte("token"), + "email": []byte("test@example.iam.gserviceaccount.com"), + "audience": []byte("https://example.com"), }, }, resources: []client.Object{testReceiverResource}, expectedResourcesAnnotated: 1, expectedResponseCode: http.StatusOK, }, + { + name: "GCR receiver rejects secret missing 'email'", + headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer token", + }, + payload: map[string]any{ + "message": map[string]any{ + "data": marshalGCRData(t, map[string]any{ + "action": "INSERT", + "digest": "us-east1-docker.pkg.dev/my-project/my-repo/app1@sha256:6ec128e26cd5...", + "tag": "us-east1-docker.pkg.dev/my-project/my-repo/app1:v1.2.3", + }), + }, + }, + receiver: &apiv1.Receiver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gcr-receiver", + }, + Spec: apiv1.ReceiverSpec{ + Type: apiv1.GCRReceiver, + SecretRef: meta.LocalObjectReference{ + Name: "gcr-token", + }, + Resources: []apiv1.CrossNamespaceObjectReference{ + { + APIVersion: apiv1.GroupVersion.String(), + Kind: apiv1.ReceiverKind, + Name: "test-resource", + }, + }, + }, + Status: apiv1.ReceiverStatus{ + WebhookPath: apiv1.ReceiverWebhookPath, + Conditions: []metav1.Condition{{Type: meta.ReadyCondition, Status: metav1.ConditionTrue}}, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gcr-token", + }, + Data: map[string][]byte{ + "token": []byte("token"), + "audience": []byte("https://example.com"), + }, + }, + resources: []client.Object{testReceiverResource}, + expectedResourcesAnnotated: 0, + expectedResponseCode: http.StatusBadRequest, + }, + { + name: "GCR receiver rejects secret missing 'audience'", + headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer token", + }, + payload: map[string]any{ + "message": map[string]any{ + "data": marshalGCRData(t, map[string]any{ + "action": "INSERT", + "digest": "us-east1-docker.pkg.dev/my-project/my-repo/app1@sha256:6ec128e26cd5...", + "tag": "us-east1-docker.pkg.dev/my-project/my-repo/app1:v1.2.3", + }), + }, + }, + receiver: &apiv1.Receiver{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gcr-receiver", + }, + Spec: apiv1.ReceiverSpec{ + Type: apiv1.GCRReceiver, + SecretRef: meta.LocalObjectReference{ + Name: "gcr-token", + }, + Resources: []apiv1.CrossNamespaceObjectReference{ + { + APIVersion: apiv1.GroupVersion.String(), + Kind: apiv1.ReceiverKind, + Name: "test-resource", + }, + }, + }, + Status: apiv1.ReceiverStatus{ + WebhookPath: apiv1.ReceiverWebhookPath, + Conditions: []metav1.Condition{{Type: meta.ReadyCondition, Status: metav1.ConditionTrue}}, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gcr-token", + }, + Data: map[string][]byte{ + "token": []byte("token"), + "email": []byte("test@example.iam.gserviceaccount.com"), + }, + }, + resources: []client.Object{testReceiverResource}, + expectedResourcesAnnotated: 0, + expectedResponseCode: http.StatusBadRequest, + }, { name: "CEL filtering a Nexus receiver", headers: map[string]string{ diff --git a/internal/server/receiver_handlers.go b/internal/server/receiver_handlers.go index 117756e26..ad9065914 100644 --- a/internal/server/receiver_handlers.go +++ b/internal/server/receiver_handlers.go @@ -427,16 +427,14 @@ func (s *ReceiverServer) validate(ctx context.Context, receiver apiv1.Receiver, } expectedEmail := string(secret.Data["email"]) - // TODO: in Flux 2.9, require the email. this will be a breaking change. - // if expectedEmail == "" { - // return fmt.Errorf("invalid secret data: required field 'email' for GCR receiver") - // } + if expectedEmail == "" { + return fmt.Errorf("invalid secret data: required field 'email' for GCR receiver") + } expectedAudience := string(secret.Data["audience"]) - // TODO: in Flux 2.9, require the audience. this will be a breaking change. - // if expectedAudience == "" { - // return fmt.Errorf("invalid secret data: required field 'audience' for GCR receiver") - // } + if expectedAudience == "" { + return fmt.Errorf("invalid secret data: required field 'audience' for GCR receiver") + } authenticate := authenticateGCRRequest if s.gcrTokenValidator != nil { @@ -636,8 +634,7 @@ func authenticateGCRRequest(ctx context.Context, bearer string, expectedEmail st // Verify the token was issued for the expected service account. email, _ := payload.Claims["email"].(string) emailVerified, _ := payload.Claims["email_verified"].(bool) - // TODO: in Flux 2.9, require the email (remove `expectedEmail != "" &&`). this will be a breaking change. - if expectedEmail != "" && email != expectedEmail { + if email != expectedEmail { return fmt.Errorf("token email is '%s', want '%s'", email, expectedEmail) } if !emailVerified {