Skip to content

Commit ff7c579

Browse files
authored
block activation mode update when vpro is active (#875)
1 parent e02963e commit ff7c579

File tree

4 files changed

+254
-2
lines changed

4 files changed

+254
-2
lines changed

apiv2/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.9.10-dev
1+
2.9.10

apiv2/internal/server/host.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,55 @@ func (is *InventorygRPCServer) handleConsecutivePowerReset(
573573
resourceID, invHost.DesiredPowerState)
574574
}
575575

576+
// validateAmtControlModeChange validates that amtControlMode cannot be changed
577+
// when both desiredAmtState and currentAmtState are AMT_STATE_PROVISIONED.
578+
func (is *InventorygRPCServer) validateAmtControlModeChange(
579+
ctx context.Context,
580+
resourceID string,
581+
fieldmask *fieldmaskpb.FieldMask,
582+
) error {
583+
// Check if amtControlMode is in the field mask
584+
amtControlModeInFieldMask := false
585+
for _, path := range fieldmask.GetPaths() {
586+
if path == inv_computev1.HostResourceFieldAmtControlMode {
587+
amtControlModeInFieldMask = true
588+
break
589+
}
590+
}
591+
592+
if !amtControlModeInFieldMask {
593+
return nil
594+
}
595+
596+
// Get the current host state to check AMT states
597+
currentHostRes, err := is.InvClient.Get(ctx, resourceID)
598+
if err != nil {
599+
zlog.Warn().Err(err).Msgf("Could not retrieve current host state for %s", resourceID)
600+
return errors.Wrap(err)
601+
}
602+
603+
currentHost := currentHostRes.GetResource().GetHost()
604+
if currentHost == nil {
605+
return nil
606+
}
607+
608+
desiredAmtState := currentHost.GetDesiredAmtState()
609+
currentAmtState := currentHost.GetCurrentAmtState()
610+
611+
zlog.Debug().Msgf("Host %s AMT state check: desiredAmtState=%v, currentAmtState=%v",
612+
resourceID, desiredAmtState, currentAmtState)
613+
614+
// Block amtControlMode change if both states are PROVISIONED
615+
if desiredAmtState == inv_computev1.AmtState_AMT_STATE_PROVISIONED &&
616+
currentAmtState == inv_computev1.AmtState_AMT_STATE_PROVISIONED {
617+
return errors.Errorfc(codes.FailedPrecondition,
618+
"cannot modify amtControlMode when host %s is already provisioned (desiredAmtState=%v, currentAmtState=%v)",
619+
resourceID, desiredAmtState, currentAmtState)
620+
}
621+
622+
return nil
623+
}
624+
576625
// Update a host. (PUT).
577626
func (is *InventorygRPCServer) UpdateHost(
578627
ctx context.Context,
@@ -635,6 +684,11 @@ func (is *InventorygRPCServer) PatchHost(
635684
return nil, errors.Wrap(err)
636685
}
637686

687+
// Validate amtControlMode changes - not allowed when already provisioned
688+
if errValidate := is.validateAmtControlModeChange(ctx, req.GetResourceId(), fieldmask); errValidate != nil {
689+
return nil, errValidate
690+
}
691+
638692
invRes := &inventory.Resource{
639693
Resource: &inventory.Resource_Host{
640694
Host: invHost,

apiv2/internal/server/host_test.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,3 +1461,201 @@ func updateInstanceAttestationStatusIndicator(
14611461
_, err := inv_testing.TestClients[inv_testing.RMClient].Update(ctx, instanceID, fieldMask, updateRes)
14621462
require.NoError(t, err)
14631463
}
1464+
1465+
//nolint:funlen // Test functions are long but necessary to test all the cases.
1466+
func TestHost_PatchAmtControlModeValidation(t *testing.T) {
1467+
mockedClient := newMockedInventoryTestClient()
1468+
server := inv_server.InventorygRPCServer{InvClient: mockedClient}
1469+
1470+
cases := []struct {
1471+
name string
1472+
mocks func() []*mock.Call
1473+
ctx context.Context
1474+
req *restv1.PatchHostRequest
1475+
wantErr bool
1476+
description string
1477+
}{
1478+
{
1479+
name: "Block amtControlMode change when both states are PROVISIONED",
1480+
mocks: func() []*mock.Call {
1481+
currentHost := &inv_computev1.HostResource{
1482+
ResourceId: "host-12345678",
1483+
DesiredAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1484+
CurrentAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1485+
AmtControlMode: inv_computev1.AmtControlMode_AMT_CONTROL_MODE_CCM,
1486+
}
1487+
return []*mock.Call{
1488+
mockedClient.On("Get", mock.Anything, "host-12345678").
1489+
Return(&inventory.GetResourceResponse{
1490+
Resource: &inventory.Resource{
1491+
Resource: &inventory.Resource_Host{Host: currentHost},
1492+
},
1493+
}, nil).Once(),
1494+
}
1495+
},
1496+
ctx: context.Background(),
1497+
req: &restv1.PatchHostRequest{
1498+
ResourceId: "host-12345678",
1499+
Host: &computev1.HostResource{
1500+
AmtControlMode: computev1.AmtControlMode_AMT_CONTROL_MODE_ACM,
1501+
},
1502+
FieldMask: &fieldmaskpb.FieldMask{
1503+
Paths: []string{computev1.HostResourceFieldAmtControlMode},
1504+
},
1505+
},
1506+
wantErr: true,
1507+
description: "Should reject amtControlMode change when host is fully provisioned",
1508+
},
1509+
{
1510+
name: "Allow amtControlMode change when desiredAmtState is not PROVISIONED",
1511+
mocks: func() []*mock.Call {
1512+
currentHost := &inv_computev1.HostResource{
1513+
ResourceId: "host-12345678",
1514+
DesiredAmtState: inv_computev1.AmtState_AMT_STATE_UNPROVISIONED,
1515+
CurrentAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1516+
AmtControlMode: inv_computev1.AmtControlMode_AMT_CONTROL_MODE_CCM,
1517+
}
1518+
return []*mock.Call{
1519+
mockedClient.On("Get", mock.Anything, "host-12345678").
1520+
Return(&inventory.GetResourceResponse{
1521+
Resource: &inventory.Resource{
1522+
Resource: &inventory.Resource_Host{Host: currentHost},
1523+
},
1524+
}, nil).Once(),
1525+
mockedClient.On("Update", mock.Anything, "host-12345678", mock.Anything, mock.Anything).
1526+
Return(&inventory.Resource{
1527+
Resource: &inventory.Resource_Host{Host: currentHost},
1528+
}, nil).Once(),
1529+
}
1530+
},
1531+
ctx: context.Background(),
1532+
req: &restv1.PatchHostRequest{
1533+
ResourceId: "host-12345678",
1534+
Host: &computev1.HostResource{
1535+
AmtControlMode: computev1.AmtControlMode_AMT_CONTROL_MODE_ACM,
1536+
},
1537+
FieldMask: &fieldmaskpb.FieldMask{
1538+
Paths: []string{computev1.HostResourceFieldAmtControlMode},
1539+
},
1540+
},
1541+
wantErr: false,
1542+
description: "Should allow amtControlMode change when desiredAmtState is not PROVISIONED",
1543+
},
1544+
{
1545+
name: "Allow amtControlMode change when currentAmtState is not PROVISIONED",
1546+
mocks: func() []*mock.Call {
1547+
currentHost := &inv_computev1.HostResource{
1548+
ResourceId: "host-12345678",
1549+
DesiredAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1550+
CurrentAmtState: inv_computev1.AmtState_AMT_STATE_UNPROVISIONED,
1551+
AmtControlMode: inv_computev1.AmtControlMode_AMT_CONTROL_MODE_CCM,
1552+
}
1553+
return []*mock.Call{
1554+
mockedClient.On("Get", mock.Anything, "host-12345678").
1555+
Return(&inventory.GetResourceResponse{
1556+
Resource: &inventory.Resource{
1557+
Resource: &inventory.Resource_Host{Host: currentHost},
1558+
},
1559+
}, nil).Once(),
1560+
mockedClient.On("Update", mock.Anything, "host-12345678", mock.Anything, mock.Anything).
1561+
Return(&inventory.Resource{
1562+
Resource: &inventory.Resource_Host{Host: currentHost},
1563+
}, nil).Once(),
1564+
}
1565+
},
1566+
ctx: context.Background(),
1567+
req: &restv1.PatchHostRequest{
1568+
ResourceId: "host-12345678",
1569+
Host: &computev1.HostResource{
1570+
AmtControlMode: computev1.AmtControlMode_AMT_CONTROL_MODE_ACM,
1571+
},
1572+
FieldMask: &fieldmaskpb.FieldMask{
1573+
Paths: []string{computev1.HostResourceFieldAmtControlMode},
1574+
},
1575+
},
1576+
wantErr: false,
1577+
description: "Should allow amtControlMode change when currentAmtState is not PROVISIONED",
1578+
},
1579+
{
1580+
name: "Allow patch without amtControlMode in field mask",
1581+
mocks: func() []*mock.Call {
1582+
currentHost := &inv_computev1.HostResource{
1583+
ResourceId: "host-12345678",
1584+
DesiredAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1585+
CurrentAmtState: inv_computev1.AmtState_AMT_STATE_PROVISIONED,
1586+
AmtControlMode: inv_computev1.AmtControlMode_AMT_CONTROL_MODE_CCM,
1587+
Name: "updated-host",
1588+
}
1589+
return []*mock.Call{
1590+
mockedClient.On("Update", mock.Anything, "host-12345678", mock.Anything, mock.Anything).
1591+
Return(&inventory.Resource{
1592+
Resource: &inventory.Resource_Host{Host: currentHost},
1593+
}, nil).Once(),
1594+
}
1595+
},
1596+
ctx: context.Background(),
1597+
req: &restv1.PatchHostRequest{
1598+
ResourceId: "host-12345678",
1599+
Host: &computev1.HostResource{
1600+
Name: "updated-host",
1601+
},
1602+
FieldMask: &fieldmaskpb.FieldMask{
1603+
Paths: []string{"name"},
1604+
},
1605+
},
1606+
wantErr: false,
1607+
description: "Should allow patch when amtControlMode is not in field mask even if host is provisioned",
1608+
},
1609+
{
1610+
name: "Allow amtControlMode change when both states are UNSPECIFIED",
1611+
mocks: func() []*mock.Call {
1612+
currentHost := &inv_computev1.HostResource{
1613+
ResourceId: "host-12345678",
1614+
DesiredAmtState: inv_computev1.AmtState_AMT_STATE_UNSPECIFIED,
1615+
CurrentAmtState: inv_computev1.AmtState_AMT_STATE_UNSPECIFIED,
1616+
AmtControlMode: inv_computev1.AmtControlMode_AMT_CONTROL_MODE_UNSPECIFIED,
1617+
}
1618+
return []*mock.Call{
1619+
mockedClient.On("Get", mock.Anything, "host-12345678").
1620+
Return(&inventory.GetResourceResponse{
1621+
Resource: &inventory.Resource{
1622+
Resource: &inventory.Resource_Host{Host: currentHost},
1623+
},
1624+
}, nil).Once(),
1625+
mockedClient.On("Update", mock.Anything, "host-12345678", mock.Anything, mock.Anything).
1626+
Return(&inventory.Resource{
1627+
Resource: &inventory.Resource_Host{Host: currentHost},
1628+
}, nil).Once(),
1629+
}
1630+
},
1631+
ctx: context.Background(),
1632+
req: &restv1.PatchHostRequest{
1633+
ResourceId: "host-12345678",
1634+
Host: &computev1.HostResource{
1635+
AmtControlMode: computev1.AmtControlMode_AMT_CONTROL_MODE_ACM,
1636+
},
1637+
FieldMask: &fieldmaskpb.FieldMask{
1638+
Paths: []string{computev1.HostResourceFieldAmtControlMode},
1639+
},
1640+
},
1641+
wantErr: false,
1642+
description: "Should allow amtControlMode change when both states are UNSPECIFIED",
1643+
},
1644+
}
1645+
1646+
for _, tc := range cases {
1647+
t.Run(tc.name, func(t *testing.T) {
1648+
if tc.mocks != nil {
1649+
tc.mocks()
1650+
}
1651+
1652+
reply, err := server.PatchHost(tc.ctx, tc.req)
1653+
if tc.wantErr {
1654+
assert.Error(t, err, tc.description)
1655+
return
1656+
}
1657+
assert.NoError(t, err, tc.description)
1658+
assert.NotNil(t, reply, tc.description)
1659+
})
1660+
}
1661+
}

apiv2/internal/server/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TruncateUint64ToUint32(value uint64) uint32 {
7272
zlog.Warn().Msgf("uint64 value %d exceeds uint32 max limit, truncating", value)
7373
}
7474

75-
return uint32(value & math.MaxUint32) //nolint:gosec // Mask the lower 32 bits.
75+
return uint32(value & math.MaxUint32) //nolint:gosec,nolintlint // G115: intentional truncation
7676
}
7777

7878
// SafeUint32Toint32 safely converts a uint32 to a int32.

0 commit comments

Comments
 (0)