Skip to content

Commit

Permalink
cli: factor out genpolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
burgerdev committed Jul 29, 2024
1 parent eb2ba85 commit f94bbff
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 152 deletions.
8 changes: 0 additions & 8 deletions cli/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,13 @@ const (
workloadOwnerPEM = "workload-owner.pem"
seedshareOwnerPEM = "seedshare-owner.pem"
manifestFilename = "manifest.json"
settingsFilename = "settings.json"
seedSharesFilename = "seed-shares.json"
rulesFilename = "rules.rego"
layersCacheFilename = "layers-cache.json"
verifyDir = "verify"
cacheDirEnv = "CONTRAST_CACHE_DIR"
)

var (
//go:embed assets/genpolicy
genpolicyBin []byte
//go:embed assets/genpolicy-settings.json
defaultGenpolicySettings []byte
//go:embed assets/genpolicy-rules.rego
defaultRules []byte
// ReleaseImageReplacements contains the image replacements used by contrast.
//go:embed assets/image-replacements.txt
ReleaseImageReplacements []byte
Expand Down
165 changes: 28 additions & 137 deletions cli/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,19 @@
package cmd

import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/edgelesssys/contrast/internal/embedbin"
"github.com/edgelesssys/contrast/cli/genpolicy"
"github.com/edgelesssys/contrast/internal/kuberesource"
"github.com/edgelesssys/contrast/internal/manifest"
"github.com/edgelesssys/contrast/node-installer/platforms"
Expand Down Expand Up @@ -60,9 +54,9 @@ subcommands.`,
RunE: withTelemetry(runGenerate),
}

cmd.Flags().StringP("policy", "p", rulesFilename, "path to policy (.rego) file")
cmd.Flags().StringP("settings", "s", settingsFilename, "path to settings (.json) file")
cmd.Flags().StringP("genpolicy-cache-path", "c", layersCacheFilename, "path to cache for the cache (.json) file containing the image layers")
cmd.Flags().StringP("policy", "p", "", "path to policy (.rego) file")
cmd.Flags().StringP("settings", "s", "", "path to settings (.json) file")
cmd.Flags().StringP("genpolicy-cache-path", "c", "", "path to cache for the cache (.json) file containing the image layers")
cmd.Flags().StringP("manifest", "m", manifestFilename, "path to manifest (.json) file")
cmd.Flags().String("reference-values", "",
fmt.Sprintf("set the default reference values used for attestation (one of: %s)",
Expand Down Expand Up @@ -106,7 +100,7 @@ func runGenerate(cmd *cobra.Command, args []string) error {
}
fmt.Fprintln(cmd.OutOrStdout(), "✔️ Patched targets")

if err := generatePolicies(cmd.Context(), flags.policyPath, flags.settingsPath, flags.genpolicyCachePath, paths, log); err != nil {
if err := generatePolicies(cmd.Context(), flags, paths, log); err != nil {
return fmt.Errorf("failed to generate policies: %w", err)
}
fmt.Fprintln(cmd.OutOrStdout(), "✔️ Generated workload policy annotations")
Expand Down Expand Up @@ -239,36 +233,39 @@ func filterNonCoCoRuntime(runtimeClassNamePrefix string, paths []string, logger
return filtered
}

func generatePolicies(ctx context.Context, regoRulesPath, policySettingsPath, genpolicyCachePath string, yamlPaths []string, logger *slog.Logger) error {
if err := createFileWithDefault(policySettingsPath, 0o644, func() ([]byte, error) { return defaultGenpolicySettings, nil }); err != nil {
return fmt.Errorf("creating default policy file: %w", err)
func generatePolicies(ctx context.Context, flags *generateFlags, yamlPaths []string, logger *slog.Logger) error {
runner, err := genpolicy.New(flags.referenceValuesPlatform)
if err != nil {
return fmt.Errorf("creating genpolicy runner: %w", err)
}
if err := createFileWithDefault(regoRulesPath, 0o644, func() ([]byte, error) { return defaultRules, nil }); err != nil {
return fmt.Errorf("creating default policy.rego file: %w", err)
if flags.genpolicyCachePath != "" {
runner.CacheFile = flags.genpolicyCachePath
}
binaryInstallDir, err := installDir()
if err != nil {
return fmt.Errorf("failed to get install dir: %w", err)
if flags.policyPath != "" {
content, err := os.ReadFile(flags.policyPath)
if err != nil {
return fmt.Errorf("reading policy file: %w", err)
}
runner.Rules = content
}
genpolicyInstall, err := embedbin.New().Install(binaryInstallDir, genpolicyBin)
if err != nil {
return fmt.Errorf("failed to install genpolicy: %w", err)
if flags.settingsPath != "" {
content, err := os.ReadFile(flags.settingsPath)
if err != nil {
return fmt.Errorf("reading policy file: %w", err)
}
runner.Settings = content
}

defer func() {
if err := genpolicyInstall.Uninstall(); err != nil {
logger.Warn("Failed to uninstall genpolicy tool", "err", err)
if err := runner.Teardown(); err != nil {
logger.Warn("Error during cleanup: %v", err)
}
}()

for _, yamlPath := range yamlPaths {
policyHash, err := generatePolicyForFile(ctx, genpolicyInstall.Path(), regoRulesPath, policySettingsPath, yamlPath, genpolicyCachePath, logger)
if err != nil {
if err := runner.Run(ctx, yamlPath, logger); err != nil {
return fmt.Errorf("failed to generate policy for %s: %w", yamlPath, err)
}
if policyHash == [32]byte{} {
continue
}

logger.Info("Calculated policy hash", "hash", hex.EncodeToString(policyHash[:]), "path", yamlPath)
}
return nil
}
Expand Down Expand Up @@ -413,95 +410,6 @@ func addSeedshareOwnerKeyToManifest(manifst *manifest.Manifest, keyPath string)
return nil
}

type logTranslator struct {
r *io.PipeReader
w *io.PipeWriter
logger *slog.Logger
stopDoneC chan struct{}
}

func newLogTranslator(logger *slog.Logger) logTranslator {
r, w := io.Pipe()
l := logTranslator{
r: r,
w: w,
logger: logger,
stopDoneC: make(chan struct{}),
}
l.startTranslate()
return l
}

func (l logTranslator) Write(p []byte) (n int, err error) {
return l.w.Write(p)
}

var genpolicyLogPrefixReg = regexp.MustCompile(`^\[[^\]\s]+\s+(\w+)\s+([^\]\s]+)\] (.*)`)

func (l logTranslator) startTranslate() {
go func() {
defer close(l.stopDoneC)
scanner := bufio.NewScanner(l.r)
for scanner.Scan() {
line := scanner.Text()
match := genpolicyLogPrefixReg.FindStringSubmatch(line)
if len(match) != 4 {
// genpolicy prints some warnings without the logger
l.logger.Warn(line)
} else {
switch match[1] {
case "ERROR":
l.logger.Error(match[3], "position", match[2])
case "WARN":
l.logger.Warn(match[3], "position", match[2])
case "INFO": // prints quite a lot, only show on debug
l.logger.Debug(match[3], "position", match[2])
}
}
}
}()
}

func (l logTranslator) stop() {
l.w.Close()
<-l.stopDoneC
}

func generatePolicyForFile(ctx context.Context, genpolicyPath, regoPath, policyPath, yamlPath, genpolicyCachePath string, logger *slog.Logger) ([32]byte, error) {
args := []string{
"--raw-out",
fmt.Sprintf("--runtime-class-names=%s", "contrast-cc"),
fmt.Sprintf("--rego-rules-path=%s", regoPath),
fmt.Sprintf("--json-settings-path=%s", policyPath),
fmt.Sprintf("--yaml-file=%s", yamlPath),
fmt.Sprintf("--layers-cache-file-path=%s", genpolicyCachePath),
}
genpolicy := exec.CommandContext(ctx, genpolicyPath, args...)
genpolicy.Env = append(genpolicy.Env, "RUST_LOG=info", "RUST_BACKTRACE=1")

logFilter := newLogTranslator(logger)
defer logFilter.stop()
var stdout bytes.Buffer
genpolicy.Stdout = &stdout
genpolicy.Stderr = logFilter

if err := genpolicy.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return [32]byte{}, fmt.Errorf("genpolicy failed with exit code %d", exitErr.ExitCode())
}
return [32]byte{}, fmt.Errorf("genpolicy failed: %w", err)
}

if stdout.Len() == 0 {
logger.Info("Policy output is empty, ignoring the file", "yamlPath", yamlPath)
return [32]byte{}, nil
}
policyHash := sha256.Sum256(stdout.Bytes())

return policyHash, nil
}

func generateWorkloadOwnerKey(flags *generateFlags) error {
if flags.disableUpdates || len(flags.workloadOwnerKeys) != 1 {
// No need to generate keys
Expand Down Expand Up @@ -588,15 +496,6 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) {
}
if workspaceDir != "" {
// Prepend default paths with workspaceDir
if !cmd.Flags().Changed("settings") {
settingsPath = filepath.Join(workspaceDir, settingsFilename)
}
if !cmd.Flags().Changed("genpolicy-cache-path") {
genpolicyCachePath = filepath.Join(workspaceDir, genpolicyCachePath)
}
if !cmd.Flags().Changed("policy") {
policyPath = filepath.Join(workspaceDir, rulesFilename)
}
if !cmd.Flags().Changed("manifest") {
manifestPath = filepath.Join(workspaceDir, manifestFilename)
}
Expand Down Expand Up @@ -664,11 +563,3 @@ func createFileWithDefault(path string, perm fs.FileMode, dflt func() ([]byte, e
_, err = file.Write(content)
return err
}

func installDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".contrast"), nil
}
43 changes: 43 additions & 0 deletions cli/genpolicy/assets/allow-all.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (c) 2023 Microsoft Corporation
#
# SPDX-License-Identifier: Apache-2.0
#

package agent_policy

default AddARPNeighborsRequest := true
default AddSwapRequest := true
default CloseStdinRequest := true
default CopyFileRequest := true
default CreateContainerRequest := true
default CreateSandboxRequest := true
default DestroySandboxRequest := true
default ExecProcessRequest := true
default GetMetricsRequest := true
default GetOOMEventRequest := true
default GuestDetailsRequest := true
default ListInterfacesRequest := true
default ListRoutesRequest := true
default MemHotplugByProbeRequest := true
default OnlineCPUMemRequest := true
default PauseContainerRequest := true
default PullImageRequest := true
default ReadStreamRequest := true
default RemoveContainerRequest := true
default RemoveStaleVirtiofsShareMountsRequest := true
default ReseedRandomDevRequest := true
default ResumeContainerRequest := true
default SetGuestDateTimeRequest := true
default SetPolicyRequest := true
default SignalProcessRequest := true
default StartContainerRequest := true
default StartTracingRequest := true
default StatsContainerRequest := true
default StopTracingRequest := true
default TtyWinResizeRequest := true
default UpdateContainerRequest := true
default UpdateEphemeralMountsRequest := true
default UpdateInterfaceRequest := true
default UpdateRoutesRequest := true
default WaitProcessRequest := true
default WriteStreamRequest := true
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit f94bbff

Please sign in to comment.