...

Source file src/github.com/Microsoft/hcsshim/internal/tools/policyenginesimulator/main.go

Documentation: github.com/Microsoft/hcsshim/internal/tools/policyenginesimulator

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"log"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  
    11  	"github.com/Microsoft/hcsshim/internal/guestpath"
    12  	rpi "github.com/Microsoft/hcsshim/internal/regopolicyinterpreter"
    13  	"github.com/Microsoft/hcsshim/pkg/securitypolicy"
    14  )
    15  
    16  type command struct {
    17  	Name  string                 `json:"name"`
    18  	Input map[string]interface{} `json:"input"`
    19  }
    20  
    21  var (
    22  	commandsPath = flag.String("commands", "", "path commands JSON file")
    23  	policyPath   = flag.String("policy", "", "path to policy Rego file")
    24  	dataPath     = flag.String("data", "", "path initial data state JSON file (optional)")
    25  	logPath      = flag.String("log", "", "path to output log file")
    26  	logLevelName = flag.String("logLevel", "Info", "None|Info|Results|Metadata")
    27  )
    28  
    29  func readCommands() []command {
    30  	content, err := os.ReadFile(*commandsPath)
    31  	if err != nil {
    32  		log.Fatal(err)
    33  	}
    34  
    35  	var commands []command
    36  	err = json.Unmarshal(content, &commands)
    37  	if err != nil {
    38  		log.Fatal(err)
    39  	}
    40  
    41  	return commands
    42  }
    43  
    44  func createInterpreter() *rpi.RegoPolicyInterpreter {
    45  	content, err := os.ReadFile(*policyPath)
    46  	if err != nil {
    47  		log.Fatal(err)
    48  	}
    49  
    50  	policyCode := string(content)
    51  
    52  	var logLevel rpi.LogLevel
    53  	switch strings.ToLower(*logLevelName) {
    54  	case "none":
    55  		logLevel = rpi.LogNone
    56  
    57  	case "info":
    58  		logLevel = rpi.LogInfo
    59  
    60  	case "results":
    61  		logLevel = rpi.LogResults
    62  
    63  	case "metadata":
    64  		logLevel = rpi.LogMetadata
    65  
    66  	default:
    67  		log.Fatalf("unrecognized log level: %s", *logLevelName)
    68  	}
    69  
    70  	var data map[string]interface{}
    71  	if len(*dataPath) > 0 {
    72  		contents, err := os.ReadFile(*dataPath)
    73  		if err != nil {
    74  			log.Fatal(err)
    75  		}
    76  
    77  		err = json.Unmarshal(contents, &data)
    78  		if err != nil {
    79  			log.Fatalf("error loading initial data state: %v", err)
    80  		}
    81  	} else {
    82  		data = map[string]interface{}{
    83  			"defaultMounts":                   []interface{}{},
    84  			"privilegedMounts":                []interface{}{},
    85  			"sandboxPrefix":                   guestpath.SandboxMountPrefix,
    86  			"hugePagesPrefix":                 guestpath.HugePagesMountPrefix,
    87  			"defaultPrivilegedCapabilities":   securitypolicy.DefaultPrivilegedCapabilities(),
    88  			"defaultUnprivilegedCapabilities": securitypolicy.DefaultUnprivilegedCapabilities(),
    89  		}
    90  	}
    91  
    92  	r, err := rpi.NewRegoPolicyInterpreter(policyCode, data)
    93  	if err != nil {
    94  		log.Fatal(err)
    95  	}
    96  
    97  	if len(*logPath) > 0 {
    98  		if _, err := os.Stat(*logPath); err == nil {
    99  			os.Remove(*logPath)
   100  		}
   101  
   102  		if logLevel == rpi.LogNone {
   103  			log.Println("logPath provided but logLevel set to None: turning off logging")
   104  		} else {
   105  			log.Printf("turning on logging to %s with level %s\n", *logPath, *logLevelName)
   106  		}
   107  		err = r.EnableLogging(*logPath, logLevel)
   108  		if err != nil {
   109  			log.Fatal(err)
   110  		}
   111  	}
   112  
   113  	r.AddModule("framework.rego", &rpi.RegoModule{Namespace: "framework", Code: securitypolicy.FrameworkCode})
   114  	r.AddModule("api.rego", &rpi.RegoModule{Namespace: "api", Code: securitypolicy.APICode})
   115  
   116  	return r
   117  }
   118  
   119  func parseNamespace(rego string) string {
   120  	lines := strings.Split(rego, "\n")
   121  	parts := strings.Split(lines[0], " ")
   122  	if parts[0] != "package" || len(parts) < 2 {
   123  		log.Fatal("package definition required on first line of Rego module")
   124  	}
   125  
   126  	namespace := parts[1]
   127  	return namespace
   128  }
   129  
   130  func loadLocalFragment(commandsDir string, input map[string]interface{}) *rpi.RegoModule {
   131  	var localPath string
   132  	var ok bool
   133  	if localPath, ok = input["local_path"].(string); !ok {
   134  		log.Println(input)
   135  		log.Fatal("'load_fragment' requires a 'local_path' member in 'input' which points to a local Rego file with the fragment logic")
   136  	}
   137  
   138  	content, err := os.ReadFile(localPath)
   139  	if err != nil {
   140  		localPath = path.Join(commandsDir, localPath)
   141  		content, err = os.ReadFile(localPath)
   142  		if err != nil {
   143  			log.Fatalf("unable to load fragment: %v", err)
   144  		}
   145  	}
   146  
   147  	code := string(content)
   148  	return &rpi.RegoModule{
   149  		Namespace: parseNamespace(code),
   150  		Feed:      input["feed"].(string),
   151  		Issuer:    input["issuer"].(string),
   152  		Code:      code,
   153  	}
   154  }
   155  
   156  func main() {
   157  	flag.Parse()
   158  	if flag.NArg() != 0 || len(*policyPath) == 0 || len(*commandsPath) == 0 {
   159  		flag.Usage()
   160  		os.Exit(1)
   161  	}
   162  
   163  	commands := readCommands()
   164  	rego := createInterpreter()
   165  
   166  	for i, command := range commands {
   167  		var fragment *rpi.RegoModule
   168  		if command.Name == "load_fragment" {
   169  			fragment = loadLocalFragment(path.Dir(*commandsPath), command.Input)
   170  			rego.AddModule(fragment.ID(), fragment)
   171  		}
   172  
   173  		result, err := rego.Query("data.policy."+command.Name, command.Input)
   174  		if err != nil {
   175  			inputJSON, _ := json.Marshal(command.Input)
   176  			log.Fatalf("query of %s with input %s failed with error %v",
   177  				command.Name,
   178  				inputJSON,
   179  				err)
   180  		}
   181  
   182  		addModule, _ := result.Bool("add_module")
   183  
   184  		removeModule := true
   185  		if fragment != nil && addModule {
   186  			removeModule = false
   187  		}
   188  
   189  		allowed, err := result.Bool("allowed")
   190  		if err != nil {
   191  			log.Fatalf("policy result missing required `allowed` key: %v", err)
   192  		}
   193  
   194  		if allowed {
   195  			log.Printf("%02d> %s ok\n", i, command.Name)
   196  		} else {
   197  			log.Printf("%02d> %s not allowed", i, command.Name)
   198  			command.Input["rule"] = command.Name
   199  			result, err = rego.Query("data.policy.reason", command.Input)
   200  			if err != nil {
   201  				log.Fatalf("unable to get reason for failure: %v", err)
   202  			}
   203  
   204  			if !result.IsEmpty() {
   205  				errors, _ := result.Value("errors")
   206  				log.Printf("Reason: %v", errors)
   207  			}
   208  		}
   209  
   210  		if removeModule && fragment != nil {
   211  			rego.RemoveModule(fragment.ID())
   212  		}
   213  	}
   214  }
   215  

View as plain text