Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: embed multiple reference values #752

Merged
merged 3 commits into from
Jul 30, 2024
Merged
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
13 changes: 13 additions & 0 deletions .github/actions/nix_tmpfs/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: use tmpfs for nix builds
description: Set Nix' build directory to a tmpfs to fix builds that don't work in the runner-provisioned ext2/ext3 filesystem

runs:
using: "composite"
steps:
- name: use tmpfs for nix builds
shell: bash
run: |
sudo mkdir -p /etc/systemd/system/nix-daemon.service.d
echo -e "[Service]\nEnvironment=TMPDIR=/dev/shm" | sudo tee /etc/systemd/system/nix-daemon.service.d/tmpfs.conf
sudo systemctl daemon-reload
sudo systemctl restart nix-daemon
1 change: 1 addition & 0 deletions .github/workflows/e2e_openssl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
echo "SYNC_ENDPOINT=http://$sync_ip:8080" | tee -a "$GITHUB_ENV"
sync_uuid=$(kubectl get configmap sync-server-fifo -o jsonpath='{.data.uuid}')
echo "SYNC_FIFO_UUID=$sync_uuid" | tee -a "$GITHUB_ENV"
- uses: ./.github/actions/nix_tmpfs
- name: Build and prepare deployments
run: |
just coordinator initializer openssl port-forwarder node-installer
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/e2e_policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
echo "SYNC_ENDPOINT=http://$sync_ip:8080" | tee -a "$GITHUB_ENV"
sync_uuid=$(kubectl get configmap sync-server-fifo -o jsonpath='{.data.uuid}')
echo "SYNC_FIFO_UUID=$sync_uuid" | tee -a "$GITHUB_ENV"
- uses: ./.github/actions/nix_tmpfs
- name: Build and prepare deployments
run: |
just coordinator initializer openssl port-forwarder node-installer
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/e2e_regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
- name: Get credentials for CI cluster
run: |
just get-credentials
- uses: ./.github/actions/nix_tmpfs
- name: Build and prepare deployments
run: |
just node-installer
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/e2e_servicemesh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ jobs:
- name: Get credentials for CI cluster
run: |
just get-credentials
- name: Set sync environemnt
- name: Set sync environment
run: |
sync_ip=$(kubectl get svc sync -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "SYNC_ENDPOINT=http://$sync_ip:8080" | tee -a "$GITHUB_ENV"
sync_uuid=$(kubectl get configmap sync-server-fifo -o jsonpath='{.data.uuid}')
echo "SYNC_FIFO_UUID=$sync_uuid" | tee -a "$GITHUB_ENV"
- uses: ./.github/actions/nix_tmpfs
- name: Build and prepare deployments
run: |
just coordinator initializer port-forwarder service-mesh-proxy node-installer
Expand Down
5 changes: 0 additions & 5 deletions cli/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package cmd
import (
"context"
_ "embed"
"fmt"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -46,10 +45,6 @@ var (
DefaultCoordinatorPolicyHash = ""
)

func runtimeHandler(digest string) string {
return fmt.Sprintf("contrast-cc-%s", digest[:32])
}

func cachedir(subdir string) (string, error) {
dir := os.Getenv(cacheDirEnv)
if dir == "" {
Expand Down
115 changes: 54 additions & 61 deletions cli/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ subcommands.`,
func runGenerate(cmd *cobra.Command, args []string) error {
flags, err := parseGenerateFlags(cmd)
if err != nil {
return fmt.Errorf("failed to parse flags: %w", err)
return fmt.Errorf("parse flags: %w", err)
}

log, err := newCLILogger(cmd)
Expand All @@ -101,23 +101,48 @@ func runGenerate(cmd *cobra.Command, args []string) error {
return err
}

if err := patchTargets(paths, flags.imageReplacementsFile, flags.skipInitializer, log); err != nil {
return fmt.Errorf("failed to patch targets: %w", err)
// generate a manifest by checking if a manifest exists and using that,
// or otherwise using a default.
var mnf *manifest.Manifest
existingManifest, err := os.ReadFile(flags.manifestPath)
if errors.Is(err, fs.ErrNotExist) {
// Manifest does not exist, create a new one
mnf, err = manifest.Default(flags.referenceValuesPlatform)
if err != nil {
return fmt.Errorf("create default manifest: %w", err)
}
} else if err != nil {
// Manifest exists but could not be read, return error
return fmt.Errorf("read existing manifest: %w", err)
} else {
// Manifest exists and was read successfully, unmarshal it
if err := json.Unmarshal(existingManifest, &mnf); err != nil {
return fmt.Errorf("unmarshal existing manifest: %w", err)
}
}

runtimeHandler, err := mnf.RuntimeHandler(flags.referenceValuesPlatform)
if err != nil {
return fmt.Errorf("get runtime handler: %w", err)
}

if err := patchTargets(paths, flags.imageReplacementsFile, runtimeHandler, flags.skipInitializer, log); err != nil {
return fmt.Errorf("patch targets: %w", err)
}
fmt.Fprintln(cmd.OutOrStdout(), "✔️ Patched targets")

if err := generatePolicies(cmd.Context(), flags.policyPath, flags.settingsPath, flags.genpolicyCachePath, paths, log); err != nil {
return fmt.Errorf("failed to generate policies: %w", err)
return fmt.Errorf("generate policies: %w", err)
}
fmt.Fprintln(cmd.OutOrStdout(), "✔️ Generated workload policy annotations")

policies, err := policiesFromKubeResources(paths)
if err != nil {
return fmt.Errorf("failed to find kube resources with policy: %w", err)
return fmt.Errorf("find kube resources with policy: %w", err)
}
policyMap, err := manifestPolicyMapFromPolicies(policies)
if err != nil {
return fmt.Errorf("failed to create policy map: %w", err)
return fmt.Errorf("create policy map: %w", err)
}

if err := generateWorkloadOwnerKey(flags); err != nil {
Expand All @@ -127,61 +152,43 @@ func runGenerate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("generating seedshare owner key: %w", err)
}

defaultManifest := manifest.Default()
switch flags.referenceValuesPlatform {
case platforms.AKSCloudHypervisorSNP:
defaultManifest = manifest.DefaultAKS()
}

defaultManifestData, err := json.MarshalIndent(&defaultManifest, "", " ")
if err != nil {
return fmt.Errorf("marshaling default manifest: %w", err)
}
manifestData, err := readFileOrDefault(flags.manifestPath, defaultManifestData)
if err != nil {
return fmt.Errorf("failed to read manifest file: %w", err)
}
var manifest *manifest.Manifest
if err := json.Unmarshal(manifestData, &manifest); err != nil {
return fmt.Errorf("failed to unmarshal manifest: %w", err)
}
manifest.Policies = policyMap
if err := manifest.Validate(); err != nil {
mnf.Policies = policyMap
if err := mnf.Validate(); err != nil {
return fmt.Errorf("validating manifest: %w", err)
}

if flags.disableUpdates {
manifest.WorkloadOwnerKeyDigests = nil
mnf.WorkloadOwnerKeyDigests = nil
} else {
for _, keyPath := range flags.workloadOwnerKeys {
if err := addWorkloadOwnerKeyToManifest(manifest, keyPath); err != nil {
if err := addWorkloadOwnerKeyToManifest(mnf, keyPath); err != nil {
return fmt.Errorf("adding workload owner key to manifest: %w", err)
}
}
}
slices.Sort(manifest.WorkloadOwnerKeyDigests)
slices.Sort(mnf.WorkloadOwnerKeyDigests)

for _, keyPath := range flags.seedshareOwnerKeys {
if err := addSeedshareOwnerKeyToManifest(manifest, keyPath); err != nil {
if err := addSeedshareOwnerKeyToManifest(mnf, keyPath); err != nil {
return fmt.Errorf("adding seedshare owner key to manifest: %w", err)
}
}
slices.Sort(manifest.SeedshareOwnerPubKeys)
slices.Sort(mnf.SeedshareOwnerPubKeys)

manifestData, err = json.MarshalIndent(manifest, "", " ")
manifestData, err := json.MarshalIndent(mnf, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal manifest: %w", err)
return fmt.Errorf("marshal manifest: %w", err)
}
if err := os.WriteFile(flags.manifestPath, append(manifestData, '\n'), 0o644); err != nil {
return fmt.Errorf("failed to write manifest: %w", err)
return fmt.Errorf("write manifest: %w", err)
}

fmt.Fprintf(cmd.OutOrStdout(), "✔️ Updated manifest %s\n", flags.manifestPath)

if hash := getCoordinatorPolicyHash(policies, log); hash != "" {
coordHashPath := filepath.Join(flags.workspaceDir, coordHashFilename)
if err := os.WriteFile(coordHashPath, []byte(hash), 0o644); err != nil {
return fmt.Errorf("failed to write coordinator policy hash: %w", err)
return fmt.Errorf("write coordinator policy hash: %w", err)
}
}

Expand All @@ -207,7 +214,7 @@ func findGenerateTargets(args []string, logger *slog.Logger) ([]string, error) {
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk %s: %w", path, err)
return nil, fmt.Errorf("walk %s: %w", path, err)
}
}
if len(paths) == 0 {
Expand All @@ -227,7 +234,7 @@ func filterNonCoCoRuntime(runtimeClassNamePrefix string, paths []string, logger
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
logger.Warn("Failed to read file", "path", path, "err", err)
logger.Warn("read file", "path", path, "err", err)
continue
}
if !bytes.Contains(data, []byte(runtimeClassNamePrefix)) {
Expand All @@ -248,21 +255,21 @@ func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath, ge
}
binaryInstallDir, err := installDir()
if err != nil {
return fmt.Errorf("failed to get install dir: %w", err)
return fmt.Errorf("get install dir: %w", err)
}
genpolicyInstall, err := embedbin.New().Install(binaryInstallDir, genpolicyBin)
if err != nil {
return fmt.Errorf("failed to install genpolicy: %w", err)
return fmt.Errorf("install genpolicy: %w", err)
}
defer func() {
if err := genpolicyInstall.Uninstall(); err != nil {
logger.Warn("Failed to uninstall genpolicy tool", "err", err)
logger.Warn("uninstall genpolicy tool", "err", err)
}
}()
for _, yamlPath := range yamlPaths {
policyHash, err := generatePolicyForFile(ctx, genpolicyInstall.Path(), regoRulesPath, policySettingsPath, yamlPath, genpolicyCachePath, logger)
if err != nil {
return fmt.Errorf("failed to generate policy for %s: %w", yamlPath, err)
return fmt.Errorf("generate policy for %s: %w", yamlPath, err)
}
if policyHash == [32]byte{} {
continue
Expand All @@ -273,7 +280,7 @@ func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath, ge
return nil
}

func patchTargets(paths []string, imageReplacementsFile string, skipInitializer bool, logger *slog.Logger) error {
func patchTargets(paths []string, imageReplacementsFile, runtimeHandler string, skipInitializer bool, logger *slog.Logger) error {
var replacements map[string]string
var err error
if imageReplacementsFile != "" {
Expand All @@ -296,11 +303,11 @@ func patchTargets(paths []string, imageReplacementsFile string, skipInitializer
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read %s: %w", path, err)
return fmt.Errorf("read %s: %w", path, err)
}
kubeObjs, err := kuberesource.UnmarshalApplyConfigurations(data)
if err != nil {
return fmt.Errorf("failed to unmarshal %s: %w", path, err)
return fmt.Errorf("unmarshal %s: %w", path, err)
}

if !skipInitializer {
Expand All @@ -314,7 +321,7 @@ func patchTargets(paths []string, imageReplacementsFile string, skipInitializer

kubeObjs = kuberesource.PatchImages(kubeObjs, replacements)

replaceRuntimeClassName := runtimeClassNamePatcher()
replaceRuntimeClassName := runtimeClassNamePatcher(runtimeHandler)
for i := range kubeObjs {
kubeObjs[i] = kuberesource.MapPodSpec(kubeObjs[i], replaceRuntimeClassName)
}
Expand All @@ -325,7 +332,7 @@ func patchTargets(paths []string, imageReplacementsFile string, skipInitializer
return err
}
if err := os.WriteFile(path, resource, os.ModePerm); err != nil {
return fmt.Errorf("failed to write %s: %w", path, err)
return fmt.Errorf("write %s: %w", path, err)
}
}
return nil
Expand Down Expand Up @@ -362,8 +369,7 @@ func injectServiceMesh(resources []any) error {
return nil
}

func runtimeClassNamePatcher() func(*applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration {
handler := runtimeHandler(manifest.TrustedMeasurement)
func runtimeClassNamePatcher(handler string) func(*applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration {
return func(spec *applycorev1.PodSpecApplyConfiguration) *applycorev1.PodSpecApplyConfiguration {
if spec.RuntimeClassName == nil || *spec.RuntimeClassName == handler {
return spec
Expand Down Expand Up @@ -633,19 +639,6 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) {
}, nil
}

// readFileOrDefault reads the file at path,
// or returns the default value if the file doesn't exist.
func readFileOrDefault(path string, deflt []byte) ([]byte, error) {
data, err := os.ReadFile(path)
if err == nil {
return data, nil
}
if !os.IsNotExist(err) {
return nil, err
}
return deflt, nil
}

// createFileWithDefault creates the file at path with the default value,
// if it doesn't exist.
func createFileWithDefault(path string, perm fs.FileMode, dflt func() ([]byte, error)) error {
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func parseVerifyFlags(cmd *cobra.Command) (*verifyFlags, error) {
}

func newCoordinatorValidateOptsGen(mnfst manifest.Manifest, hostData []byte) (*snp.StaticValidateOptsGenerator, error) {
validateOpts, err := mnfst.SNPValidateOpts()
validateOpts, err := mnfst.AKSValidateOpts()
msanft marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
Expand Down
11 changes: 5 additions & 6 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main

import (
"context"
"encoding/json"
"fmt"
"os"
"os/signal"
Expand All @@ -14,7 +15,6 @@ import (
"github.com/edgelesssys/contrast/cli/cmd"
"github.com/edgelesssys/contrast/cli/constants"
"github.com/edgelesssys/contrast/internal/manifest"
"github.com/edgelesssys/contrast/node-installer/platforms"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -43,11 +43,10 @@ func buildVersionString() string {
fmt.Fprintf(versionsWriter, "\t%s\n", image)
}
}
fmt.Fprint(versionsWriter, "\n")
fmt.Fprintf(versionsWriter, "reference values for %s platform:\n", platforms.AKSCloudHypervisorSNP.String())
fmt.Fprintf(versionsWriter, "\truntime handler:\tcontrast-cc-%s\n", manifest.TrustedMeasurement[:32])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime handler isn't printed anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there's no singular valid runtime handler anymore

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was already anticipated, that's why there was a platform-specific section in the output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do you want to roll back to how it was before, but showing everything for SNP and TDX?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just saying you shouldn't throw away the work other developers put in there without a thought. Should users construct the runtime name themselves in the future and count the offset of the prefix we are using? Is the length of the offset documented anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with changing it back, but then I'd also suggest we don't print the JSON at all. Are you fine with "reverting" that @burgerdev?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like we need to discuss the question of runtime handlers vs. reference values first - we want this to be a 1:1 mapping, right? So, should it go into ReferenceValues fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicitly, I'd say it's already there due to the presence of TrustedMeasurement in each of the ReferenceValues. But I think this is something so adjacent to the general discussion of how we want to handle these that we should discuss it in the sync today.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fix the output in a follow up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a ticket for this

fmt.Fprintf(versionsWriter, "\tlaunch digest:\t%s\n", manifest.TrustedMeasurement)
fmt.Fprintf(versionsWriter, "\tgenpolicy version:\t%s\n", constants.GenpolicyVersion)
if refValues, err := json.MarshalIndent(manifest.EmbeddedReferenceValues(), "\t", " "); err == nil {
burgerdev marked this conversation as resolved.
Show resolved Hide resolved
fmt.Fprintf(versionsWriter, "embedded reference values:\t%s\n", refValues)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reduced readability of the output a lot. If we want to keep this as json output, we should move it to the end and start the json doc in a separate line so you can cut it and process it with jq if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have a --json for this use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would mean a refactoring of the command structure, as we are currently using the builtin --version flag, not a command, right?

}
fmt.Fprintf(versionsWriter, "genpolicy version:\t%s\n", constants.GenpolicyVersion)
versionsWriter.Flush()
return versionsBuilder.String()
}
Expand Down
2 changes: 1 addition & 1 deletion coordinator/internal/authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (m *Authority) SNPValidateOpts(report *sevsnp.Report) (*validate.Options, e
return nil, fmt.Errorf("hostdata %s not found in manifest", hostData)
}

return mnfst.SNPValidateOpts()
return mnfst.AKSValidateOpts()
}

// ValidateCallback creates a certificate bundle for the verified client.
Expand Down
6 changes: 4 additions & 2 deletions coordinator/internal/authority/authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/contrast/coordinator/history"
"github.com/edgelesssys/contrast/internal/manifest"
"github.com/edgelesssys/contrast/internal/userapi"
"github.com/edgelesssys/contrast/node-installer/platforms"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
Expand Down Expand Up @@ -79,12 +80,13 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) {
policyHash := sha256.Sum256(policy)
policyHashHex := manifest.NewHexString(policyHash[:])

mnfst := manifest.DefaultAKS()
mnfst, err := manifest.Default(platforms.AKSCloudHypervisorSNP)
require.NoError(t, err)
mnfst.Policies = map[manifest.HexString][]string{policyHashHex: {"test"}}
mnfst.WorkloadOwnerKeyDigests = []manifest.HexString{keyDigest}
mnfstBytes, err := json.Marshal(mnfst)
require.NoError(t, err)
return &mnfst, mnfstBytes, [][]byte{policy}
return mnfst, mnfstBytes, [][]byte{policy}
}

func requireGauge(t *testing.T, reg *prometheus.Registry, val int) {
Expand Down
Loading