diff --git a/cmd/build.go b/cmd/build.go new file mode 100644 index 0000000..2fe3d39 --- /dev/null +++ b/cmd/build.go @@ -0,0 +1,104 @@ +/* +Copyright © 2024 Alessio Greggi + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "bufio" + "fmt" + "os" + + seccomp "github.com/alegrey91/harpoon/internal/seccomputils" + "github.com/spf13/cobra" +) + +var ( + inputDirectory string + saveProfile bool + profileName = "seccomp.json" +) + +// buildCmd represents the create args +var buildCmd = &cobra.Command{ + Use: "build", + Short: "build collects system calls from harpoon generated files and create a Seccomp profile with them", + Long: ` +`, + Example: " harpoon build", + Run: func(cmd *cobra.Command, args []string) { + files, err := os.ReadDir(inputDirectory) + if err != nil { + fmt.Printf("error reading dir content: %v", err) + return + } + + syscalls := make([]string, 0) + for _, fileObj := range files { + //fmt.Println("[" + fileObj.Name() + "]") + + file, err := os.Open(inputDirectory + "/" + fileObj.Name()) + if err != nil { + fmt.Printf("error opening file %s: %v", file.Name(), err) + return + } + defer file.Close() + + // collect system calls from file + var syscallList = make(map[string]int) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + syscall := scanner.Text() + syscallList[string(syscall)]++ + } + + // convert map to list of string + for value := range syscallList { + syscalls = append(syscalls, value) + } + } + + profile, err := seccomp.BuildProfile(syscalls) + if err != nil { + fmt.Printf("error building seccomp profile: %v", err) + } + + if saveProfile { + profileFile, err := os.Create(profileName) + if err != nil { + fmt.Printf("error creating seccomp file %s: %v\n", profileFile.Name(), err) + return + } + defer profileFile.Close() + + if err := profileFile.Chmod(0644); err != nil { + fmt.Printf("error setting permissions to %s: %v\n", profileFile.Name(), err) + } + // write to file + fmt.Fprintln(profileFile, profile) + } else { + fmt.Println(profile) + } + }, +} + +func init() { + rootCmd.AddCommand(buildCmd) + + buildCmd.Flags().StringVarP(&inputDirectory, "directory", "D", "", "Directory containing harpoon's files") + buildCmd.MarkFlagRequired("directory") + + buildCmd.Flags().BoolVarP(&saveProfile, "save-profile", "s", false, "Save profile to a file") + buildCmd.Flags().StringVarP(&profileName, "name", "n", profileName, "Save profile to a file") +} diff --git a/internal/seccomputils/seccomp.tmpl b/internal/seccomputils/seccomp.tmpl new file mode 100644 index 0000000..ceb50cc --- /dev/null +++ b/internal/seccomputils/seccomp.tmpl @@ -0,0 +1,18 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ], + "syscalls": [ + { + "names": [ + {{- range $index, $syscall := .Syscalls }} + "{{$syscall}}"{{if ne $index (sub1 (len $.Syscalls))}},{{end}} + {{- end }} + ], + "action": "SCMP_ACT_ALLOW" + } + ] +} \ No newline at end of file diff --git a/internal/seccomputils/seccomputils.go b/internal/seccomputils/seccomputils.go new file mode 100644 index 0000000..f4741c5 --- /dev/null +++ b/internal/seccomputils/seccomputils.go @@ -0,0 +1,45 @@ +package seccomputils + +import ( + "bytes" + _ "embed" + "fmt" + "html/template" +) + +//go:embed seccomp.tmpl +var SeccompProfileTemplate string + +// Data to hold the syscalls +type SeccompContent struct { + Syscalls []string +} + +// sub1 is a helper function to subtract 1 from an integer +func sub1(i int) int { + return i - 1 +} + +// BuildProfile builds the seccomp profile from the list of syscalls +func BuildProfile(syscalls []string) (string, error) { + // Parse the template + tmpl, err := template.New("seccomp").Funcs(template.FuncMap{ + "sub1": sub1, + }).Parse(SeccompProfileTemplate) + if err != nil { + return "", fmt.Errorf("failed to parse template: %w", err) + } + + // Prepare the data + data := SeccompContent{ + Syscalls: syscalls, + } + + // Execute the template + var profile bytes.Buffer + if err := tmpl.Execute(&profile, data); err != nil { + return "", fmt.Errorf("failed to execute template: %w", err) + } + + return profile.String(), nil +}