Skip to content

Commit d1b3dff

Browse files
committed
fix: fix immutability checks in infra provider state
Found a couple of bugs in the infra provider state: - For some reason, sometimes a resource update is detected where nothing other than the `.metadata.version` was changing. In these cases, the validation blocked the update. Fix this by ignoring the version field. Since we ignore the version field, we now compare the specs as well as the metadata. - Infra providers were able to modify `infra.Machine` resources when they shouldn't have. The fix above also addresses that. Signed-off-by: Utku Ozdemir <[email protected]>
1 parent 353a3c0 commit d1b3dff

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

Diff for: internal/backend/runtime/omni/infraprovider/state.go

+39-7
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,7 @@ func (st *State) Update(ctx context.Context, newResource resource.Resource, opts
142142

143143
switch newResource.Metadata().Type() {
144144
case infra.MachineRequestType, infra.InfraMachineType:
145-
oldMd := oldResource.Metadata().Copy()
146-
oldMd.Finalizers().Set(resource.Finalizers{})
147-
148-
newMd := newResource.Metadata().Copy()
149-
newMd.Finalizers().Set(resource.Finalizers{})
150-
151-
if !oldMd.Equal(newMd) {
145+
if !st.resourcesAreEqual(oldResource, newResource) {
152146
return status.Errorf(codes.PermissionDenied, "infra providers are not allowed to update %q resources other than setting finalizers", newResource.Metadata().Type())
153147
}
154148
}
@@ -163,6 +157,44 @@ func (st *State) Update(ctx context.Context, newResource resource.Resource, opts
163157
return status.Errorf(codes.NotFound, "not found")
164158
}
165159

160+
func (st *State) resourcesAreEqual(res1, res2 resource.Resource) bool {
161+
var ignoreVersion resource.Version
162+
163+
md1Copy := res1.Metadata().Copy()
164+
md2Copy := res2.Metadata().Copy()
165+
166+
md1Copy.SetVersion(ignoreVersion)
167+
md2Copy.SetVersion(ignoreVersion)
168+
md1Copy.Finalizers().Set(resource.Finalizers{})
169+
md2Copy.Finalizers().Set(resource.Finalizers{})
170+
171+
if !md1Copy.Equal(md2Copy) {
172+
return false
173+
}
174+
175+
type equaler interface {
176+
Equal(any) bool
177+
}
178+
179+
spec1 := res1.Spec()
180+
spec2 := res2.Spec()
181+
182+
if spec1 == spec2 {
183+
return true
184+
}
185+
186+
if res1.Spec() == nil || res2.Spec() == nil {
187+
return false
188+
}
189+
190+
s1, ok := res1.Spec().(equaler)
191+
if !ok {
192+
return false
193+
}
194+
195+
return s1.Equal(res2.Spec())
196+
}
197+
166198
// Destroy implements state.CoreState interface.
167199
func (st *State) Destroy(ctx context.Context, pointer resource.Pointer, option ...state.DestroyOption) error {
168200
infraProviderID, err := st.checkAuthorization(ctx, pointer.Namespace(), pointer.Type())

Diff for: internal/backend/runtime/omni/infraprovider/state_test.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ func TestInfraProviderAccess(t *testing.T) {
6565

6666
return nil
6767
}, func(t *testing.T, err error) {
68-
assert.True(t, validated.IsValidationError(err))
69-
assert.ErrorContains(t, err, "machine request spec is immutable")
68+
assert.Equal(t, codes.PermissionDenied, status.Code(err))
69+
assert.ErrorContains(t, err, `infra providers are not allowed to update "MachineRequests.omni.sidero.dev" resources other than setting finalizers`)
7070
})
7171

7272
// InfraMachine
@@ -80,7 +80,8 @@ func TestInfraProviderAccess(t *testing.T) {
8080

8181
return nil
8282
}, func(t *testing.T, err error) {
83-
require.NoError(t, err)
83+
assert.Equal(t, codes.PermissionDenied, status.Code(err))
84+
assert.ErrorContains(t, err, `infra providers are not allowed to update "InfraMachines.omni.sidero.dev" resources other than setting finalizers`)
8485
})
8586

8687
// MachineRequestStatus
@@ -209,6 +210,14 @@ func TestInternalAccess(t *testing.T) {
209210

210211
err = st.Create(ctx, mr)
211212
assert.NoError(t, err)
213+
214+
_, err = safe.StateUpdateWithConflicts(ctx, st, mr.Metadata(), func(res *infra.MachineRequest) error {
215+
res.TypedSpec().Value.TalosVersion = "v1.2.5"
216+
217+
return nil
218+
})
219+
assert.True(t, validated.IsValidationError(err))
220+
assert.ErrorContains(t, err, "machine request spec is immutable")
212221
}
213222

214223
func TestInfraProviderSpecificNamespace(t *testing.T) {

0 commit comments

Comments
 (0)