Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 85 additions & 18 deletions cli/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ func runGenerate(cmd *cobra.Command, args []string) error {
usedPlatforms.Add(flags.referenceValuesPlatform)
}

usedCPUs, coordinatorCPU, err := usedCPUsFromUnstructured(fileMap)
if err != nil {
return fmt.Errorf("determining cpu counts used in deployment: %w", err)
}

// generate a manifest by checking if a manifest exists and using that,
// or otherwise using a default.
var mnf *manifest.Manifest
Expand Down Expand Up @@ -176,6 +181,26 @@ func runGenerate(cmd *cobra.Command, args []string) error {
}
}

var filteredSNP []manifest.SNPReferenceValues
for _, snp := range mnf.ReferenceValues.SNP {
filteredMeasurements := make(map[string]manifest.HexString)
for cpuStr, meas := range snp.TrustedMeasurements {
cpu, _ := strconv.ParseUint(cpuStr, 10, 64)
if slices.Contains(usedCPUs, cpu) {
filteredMeasurements[cpuStr] = meas
}
}

if len(filteredMeasurements) > 0 {
snp.TrustedMeasurements = filteredMeasurements
if coordinatorCPU > 0 {
snp.TrustedMeasurement = filteredMeasurements[strconv.FormatUint(coordinatorCPU, 10)]
}
filteredSNP = append(filteredSNP, snp)
}
}
mnf.ReferenceValues.SNP = filteredSNP

var runtimeHandler string
if flags.referenceValuesPlatform == platforms.Unknown {
// Due to the pre generate verifiers, this code path should only be reachable when all resources have an explicit runtime class set.
Expand Down Expand Up @@ -679,6 +704,42 @@ func runtimeClassesFromUnstructured(fileMap map[string][]*unstructured.Unstructu
return runtimeClasses, nil
}

func usedCPUsFromUnstructured(fileMap map[string][]*unstructured.Unstructured) ([]uint64, uint64, error) {
used := make(map[uint64]struct{})
var coordinatorCPU uint64
for _, resources := range fileMap {
for _, r := range resources {
applyConfig, err := kuberesource.UnstructuredToApplyConfiguration(r)
if err != nil {
return nil, 0, err
}
if !isCCWorkload(applyConfig) {
continue
}

isCoord := isCoordinator(applyConfig)
kuberesource.MapPodSpec(applyConfig, func(spec *applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration {
if spec == nil {
return spec
}

totalCPUs := getPodCPUCount(spec)
used[totalCPUs] = struct{}{}
if isCoord {
coordinatorCPU = totalCPUs
}
return spec
})
}
}

var out []uint64
for k := range used {
out = append(out, k)
}
return out, coordinatorCPU, nil
}

func patchRuntimeClassName(defaultRuntimeHandler string) func(*applycorev1.PodSpecApplyConfiguration) (*applycorev1.PodSpecApplyConfiguration, error) {
return func(spec *applycorev1.PodSpecApplyConfiguration) (*applycorev1.PodSpecApplyConfiguration, error) {
if spec == nil || spec.RuntimeClassName == nil {
Expand Down Expand Up @@ -734,24 +795,8 @@ func patchIDBlockAnnotation(res any) error {
return meta, spec, nil
}

var regularContainersCPU int64
for _, container := range spec.Containers {
regularContainersCPU += getCPUCount(container.Resources)
}
var initContainersCPU int64
for _, container := range spec.InitContainers {
cpuCount := getCPUCount(container.Resources)
initContainersCPU += cpuCount
// Sidecar containers remain running alongside the actual application, consuming CPU resources
if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
regularContainersCPU += cpuCount
}
}
podLevelCPU := getCPUCount(spec.Resources)

// Convert milliCPUs to number of CPUs (rounding up), and add 1 for hypervisor overhead
totalMilliCPUs := max(regularContainersCPU, initContainersCPU, podLevelCPU)
cpuCount := strconv.FormatInt((totalMilliCPUs+999)/1000+1, 10)
totalCPUs := getPodCPUCount(spec)
cpuCount := strconv.FormatUint(totalCPUs, 10)

platform := strings.ToLower(targetPlatform.String())

Expand Down Expand Up @@ -789,6 +834,28 @@ func getCPUCount(resources *applycorev1.ResourceRequirementsApplyConfiguration)
return 0
}

func getPodCPUCount(spec *applycorev1.PodSpecApplyConfiguration) uint64 {
var regularContainersCPU int64
for _, container := range spec.Containers {
regularContainersCPU += getCPUCount(container.Resources)
}
var initContainersCPU int64
for _, container := range spec.InitContainers {
cpuCount := getCPUCount(container.Resources)
initContainersCPU += cpuCount
// Sidecar containers remain running alongside the actual application, consuming CPU resources
if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
regularContainersCPU += cpuCount
}
}
podLevelCPU := getCPUCount(spec.Resources)

// Convert milliCPUs to number of CPUs (rounding up), and add 1 for hypervisor overhead
totalMilliCPUs := max(regularContainersCPU, initContainersCPU, podLevelCPU)
totalCPUs := (totalMilliCPUs+999)/1000 + 1
return uint64(totalCPUs)
}

type generateFlags struct {
policyPath string
settingsPath string
Expand Down
28 changes: 26 additions & 2 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"os"
"os/signal"
"sort"
"strconv"
"strings"
"text/tabwriter"

Expand Down Expand Up @@ -78,8 +80,10 @@ func buildVersionString() (string, error) {
}
for _, snp := range values.SNP {
fmt.Fprintf(versionsWriter, "\t- product name:\t%s\n", snp.ProductName)
fmt.Fprintf(versionsWriter, "\t vCPUs:\t%d\n", snp.CPUs)
fmt.Fprintf(versionsWriter, "\t launch digest:\t%s\n", snp.TrustedMeasurement.String())
fmt.Fprintf(versionsWriter, "\t launch digest per vCPU count:\n")
forSortedMeasurement(snp.TrustedMeasurements, func(cpu string, meas manifest.HexString) {
fmt.Fprintf(versionsWriter, "\t %3s: %s\n", cpu, meas.String())
})
fmt.Fprint(versionsWriter, "\t default SNP TCB:\t\n")
printOptionalSVN("bootloader", snp.MinimumTCB.BootloaderVersion)
printOptionalSVN("tee", snp.MinimumTCB.TEEVersion)
Expand Down Expand Up @@ -157,3 +161,23 @@ func signalContext(ctx context.Context, sig os.Signal) (context.Context, context
func preRunRoot(cmd *cobra.Command, _ []string) {
cmd.SilenceUsage = true
}

func forSortedMeasurement(
m map[string]manifest.HexString,
fn func(cpu string, meas manifest.HexString),
) {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}

sort.Slice(keys, func(i, j int) bool {
ai, _ := strconv.Atoi(keys[i])
bi, _ := strconv.Atoi(keys[j])
return ai < bi
})

for _, k := range keys {
fn(k, m[k])
}
}
2 changes: 1 addition & 1 deletion coordinator/internal/peerrecovery/recovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte) {
SNPVersion: &svn0,
MicrocodeVersion: &svn0,
},
TrustedMeasurement: manifest.NewHexString(measurement[:]),
TrustedMeasurements: map[string]manifest.HexString{"1": manifest.NewHexString(measurement[:])},
GuestPolicy: abi.SnpPolicy{
SMT: true,
},
Expand Down
2 changes: 1 addition & 1 deletion coordinator/internal/stateguard/stateguard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) {
SNPVersion: &svn0,
MicrocodeVersion: &svn0,
},
TrustedMeasurement: manifest.NewHexString(measurement[:]),
TrustedMeasurements: map[string]manifest.HexString{"1": manifest.NewHexString(measurement[:])},
GuestPolicy: abi.SnpPolicy{
SMT: true,
},
Expand Down
2 changes: 1 addition & 1 deletion coordinator/internal/userapi/userapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,7 @@ func newManifestWithSeedshareOwner(t *testing.T) ([]byte, [][]byte) {
SNPVersion: &svn0,
MicrocodeVersion: &svn0,
},
TrustedMeasurement: manifest.NewHexString(measurement[:]),
TrustedMeasurements: map[string]manifest.HexString{"1": manifest.NewHexString(measurement[:])},
GuestPolicy: abi.SnpPolicy{
SMT: true,
},
Expand Down
7 changes: 5 additions & 2 deletions dev-docs/frozen/security-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ The report includes the following information:
The manifest, enforced by the Contrast coordinator, contains reference values used to verify all application pods. It can be seen as the trusted reference state of the deployment. The manifest includes:

- **Policies:** One cryptographic hash per pod, representing its enforced runtime policy.
- **ReferenceValues**: The launch digests of the CVMs, based on AMD SEV-SNP. This doesn't include any application code but tracks the setup of the CVM. Confidential Pods on the same CPU have the same reference values.
- **ReferenceValues**: The launch digests of the CVMs, based on AMD SEV-SNP. This doesn't include any application code but tracks the setup of the CVM. Reference values are grouped by CPU count, as the measurement depends on the number of vCPUs assigned to the VM.
- **WorkloadOwnerKeyDigests**: A public key digest used to authenticate subsequent manifest updates.
- **SeedshareOwnerPubKeys**: Used for coordinator recovery. For details, see [later sections](#secret-recovery).

Expand Down Expand Up @@ -127,7 +127,10 @@ Here is an example manifest:
"MicrocodeVersion": 72
},
"ProductName": "Genoa",
"TrustedMeasurement": "92a34339f1e1ec94b911830cafa875082d4f51b9805f3c2638ce468c6fda038be5acaca52fea5cb767e18cc1edfb1f7c"
"TrustedMeasurements": {
"1": "92a34339f1e1ec94b911830cafa875082d4f51b9805f3c2638ce468c6fda038be5acaca52fea5cb767e18cc1edfb1f7c",
"2": "..."
}
}
]
},
Expand Down
13 changes: 9 additions & 4 deletions docs/docs/architecture/components/manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ The manifest has the following higher level structure:
"snp": [
{
"ProductName": "<product-name>",
"TrustedMeasurement": "<trusted-measurement>",
"TrustedMeasurements": {
"1": "<trusted-measurement-1-cpu>",
"2": "<trusted-measurement-2-cpus>",
...
},
"MinimumTCB": { },
"GuestPolicy": { },
"PlatformInfo": { }
Expand Down Expand Up @@ -125,13 +129,14 @@ The Coordinator will accept a workload if its attestation report matches _any_ o
The product name of your platform.
`Milan` and `Genoa` are supported by Contrast.

### `ReferenceValues.snp.*.TrustedMeasurement` {#snp-trusted-measurement}
### `ReferenceValues.snp.*.TrustedMeasurements` {#snp-trusted-measurements}

The `TrustedMeasurement` is a hash over the initial memory contents and state of the confidential VM.
The `TrustedMeasurements` is a map of vCPU counts to their respective launch measurements.
A launch measurement is a hash over the initial memory contents and state of the confidential VM.
It covers the guest firmware, the initrd and kernel as well as the kernel command line.
The kernel command line contains the dm-verity hash of the root filesystem, which contains all Contrast components that run inside the guest.

It's the (launch) `MEASUREMENT` from the SNP `ATTESTATION_REPORT`, according to Table 23 in the [SEV ABI Spec].
The values are (launch) `MEASUREMENT`s from the SNP `ATTESTATION_REPORT`, according to Table 23 in the [SEV ABI Spec].

### `ReferenceValues.snp.*.MinimumTCB` {#snp-minimum-tcb}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Altogether, setting the limit to 10x the compressed image size should be suffici
:::warning

Each Contrast pod requires one CPU by default.
Containers may request additional CPUs, up to a total of 8 CPUs per pod.
Containers may request additional CPUs, up to a total of 220 CPUs per pod.
Please note that fractional CPU requests are always rounded up to the nearest whole number, meaning a Pod with one container requesting 0.2 CPUs will require 2 CPUs in total.

:::
Expand Down
6 changes: 5 additions & 1 deletion internal/kuberesource/sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ func OpenSSL() []any {
WithContainerPort(443),
).
WithResources(ResourceRequirements().
WithMemoryLimitAndRequest(250),
WithMemoryLimitAndRequest(250).
WithLimits(corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("250Mi"),
corev1.ResourceCPU: resource.MustParse("2"),
}),
).
WithReadinessProbe(Probe().
WithInitialDelaySeconds(1).
Expand Down
Loading
Loading