@@ -3,89 +3,21 @@ package cmd
33import (
44 "fmt"
55 "html/template"
6- "net/http"
76 "os"
87 "path/filepath"
98 "strings"
109
1110 "github.com/go-git/go-git/v5"
1211 "github.com/spf13/cobra"
1312 "github.com/spf13/viper"
14- "gopkg.in/yaml.v2"
15- )
16-
17- type ControlCatalog struct {
18- CategoryIDFriendly string
19- ServiceName string
20- TestSuites map [string ][]string
21-
22- Metadata Metadata `yaml:"metadata"`
23-
24- Controls []Control `yaml:"controls"`
25- Features []Feature `yaml:"features"`
26- Threats []Threat `yaml:"threats"`
27-
28- LatestReleaseDetails ReleaseDetails `yaml:"latest_release_details"`
29- }
30-
31- // Metadata is a struct that represents the metadata.yaml file
32- type Metadata struct {
33- Title string `yaml:"title"`
34- ID string `yaml:"id"`
35- Description string `yaml:"description"`
36- ReleaseDetails []ReleaseDetails `yaml:"release_details"`
37- }
3813
39- type ReleaseDetails struct {
40- Version string `yaml:"version"`
41- AssuranceLevel string `yaml:"assurance_level"`
42- ThreatModelURL string `yaml:"threat_model_url"`
43- ThreatModelAuthor string `yaml:"threat_model_author"`
44- RedTeam string `yaml:"red_team"`
45- RedTeamExerciseURL string `yaml:"red_team_exercise_url"`
46- ReleaseManager ReleaseManager `yaml:"release_manager"`
47- ChangeLog []string `yaml:"change_log"`
48- }
49-
50- type ReleaseManager struct {
51- Name string `yaml:"name"`
52- GithubId string `yaml:"github_id"`
53- Company string `yaml:"company"`
54- Summary string `yaml:"summary"`
55- }
56-
57- type Feature struct {
58- ID string `yaml:"id"`
59- Title string `yaml:"title"`
60- Description string `yaml:"description"`
61- }
62-
63- type Threat struct {
64- ID string `yaml:"id"`
65- Title string `yaml:"title"`
66- Description string `yaml:"description"`
67- Features []string `yaml:"features"`
68- MITRE []string `yaml:"mitre_attack"`
69- }
70-
71- type Control struct {
72- IDFriendly string
73- ID string `yaml:"id"`
74- Title string `yaml:"title"`
75- Objective string `yaml:"objective"`
76- ControlFamily string `yaml:"control_family"`
77- Threats []string `yaml:"threats"`
78- NISTCSF string `yaml:"nist_csf"`
79- MITREATTACK string `yaml:"mitre_attack"`
80- ControlMappings map [string ]interface {} `yaml:"control_mappings"`
81- TestRequirements []TestRequirement `yaml:"test_requirements"`
82- }
14+ "github.com/revanite-io/sci/pkg/layer2"
15+ )
8316
84- type TestRequirement struct {
85- IDFriendly string
86- ID string `yaml:"id"`
87- Text string `yaml:"text"`
88- TLPLevels []string `yaml:"tlp_levels"`
17+ type CatalogData struct {
18+ layer2.Catalog
19+ ServiceName string
20+ TestSuites map [string ][]string
8921}
9022
9123var TemplatesDir string
@@ -102,8 +34,6 @@ var genPluginCmd = &cobra.Command{
10234}
10335
10436func init () {
105- rootCmd .AddCommand (genPluginCmd )
106-
10737 genPluginCmd .PersistentFlags ().StringP ("source-path" , "p" , "" , "The source file to generate the plugin from." )
10838 genPluginCmd .PersistentFlags ().StringP ("local-templates" , "" , "" , "Path to a directory to use instead of downloading the latest templates." )
10939 genPluginCmd .PersistentFlags ().StringP ("service-name" , "n" , "" , "The name of the service (e.g. 'ECS, AKS, GCS')." )
@@ -113,6 +43,8 @@ func init() {
11343 viper .BindPFlag ("local-templates" , genPluginCmd .PersistentFlags ().Lookup ("local-templates" ))
11444 viper .BindPFlag ("service-name" , genPluginCmd .PersistentFlags ().Lookup ("service-name" ))
11545 viper .BindPFlag ("output-dir" , genPluginCmd .PersistentFlags ().Lookup ("output-dir" ))
46+
47+ rootCmd .AddCommand (genPluginCmd )
11648}
11749
11850func generatePlugin () {
@@ -189,8 +121,18 @@ func setupTemplatesDir() error {
189121 return err
190122}
191123
192- func generateFileFromTemplate (data ControlCatalog , templatePath , OutputDir string ) error {
193- tmpl , err := template .ParseFiles (templatePath )
124+ func generateFileFromTemplate (data CatalogData , templatePath , OutputDir string ) error {
125+ templateContent , err := os .ReadFile (templatePath )
126+ if err != nil {
127+ return fmt .Errorf ("error reading template file %s: %w" , templatePath , err )
128+ }
129+
130+ tmpl , err := template .New ("plugin" ).Funcs (template.FuncMap {
131+ "prettify" : func (s string ) string {
132+ return strings .TrimSpace (strings .ReplaceAll (
133+ strings .ReplaceAll (s , "\n " , " " ), "." , "_" ))
134+ },
135+ }).Parse (string (templateContent ))
194136 if err != nil {
195137 return fmt .Errorf ("error parsing template file %s: %w" , templatePath , err )
196138 }
@@ -201,14 +143,17 @@ func generateFileFromTemplate(data ControlCatalog, templatePath, OutputDir strin
201143 }
202144
203145 outputPath := filepath .Join (OutputDir , strings .TrimSuffix (relativePath , ".txt" ))
146+
204147 err = os .MkdirAll (filepath .Dir (outputPath ), os .ModePerm )
205148 if err != nil {
206149 return fmt .Errorf ("error creating directories for %s: %w" , outputPath , err )
207150 }
151+
208152 outputFile , err := os .Create (outputPath )
209153 if err != nil {
210154 return fmt .Errorf ("error creating output file %s: %w" , outputPath , err )
211155 }
156+
212157 defer outputFile .Close ()
213158
214159 err = tmpl .Execute (outputFile , data )
@@ -219,76 +164,26 @@ func generateFileFromTemplate(data ControlCatalog, templatePath, OutputDir strin
219164 return nil
220165}
221166
222- func readData () (data ControlCatalog , err error ) {
223- if strings .HasPrefix (SourcePath , "http" ) {
224- data , err = readYAMLURL ()
225- } else {
226- data , err = readYAMLFile ()
227- }
167+ func readData () (data CatalogData , err error ) {
168+ err = data .LoadControlFamiliesFile (SourcePath )
228169 if err != nil {
229170 return
230171 }
231172
232173 data .TestSuites = make (map [string ][]string )
233- data .CategoryIDFriendly = strings .ReplaceAll (data .Metadata .ID , "." , "_" )
234174
235- for i := range data .Controls {
236- fmt .Println (data .Controls [i ].ID )
237- data .Controls [i ].IDFriendly = strings .ReplaceAll (data .Controls [i ].ID , "." , "_" )
238- // loop over objectives in test_requirements and replace newlines with empty string
239- for j , testReq := range data .Controls [i ].TestRequirements {
240- // Some test requirements have newlines in them, which breaks the template
241- data .Controls [i ].TestRequirements [j ].Text = strings .TrimSpace (strings .ReplaceAll (testReq .Text , "\n " , " " ))
242- // Replace periods with underscores for the friendly ID
243- data .Controls [i ].TestRequirements [j ].IDFriendly = strings .ReplaceAll (testReq .ID , "." , "_" )
244-
245- // Add the test ID to the TestSuites map for each TLP level
246- for _ , tlpLevel := range testReq .TLPLevels {
247- if data .TestSuites [tlpLevel ] == nil {
248- data .TestSuites [tlpLevel ] = []string {}
175+ for i , family := range data .ControlFamilies {
176+ for j := range family .Controls {
177+ for _ , testReq := range data .ControlFamilies [i ].Controls [j ].Requirements {
178+ // Add the test ID to the TestSuites map for each TLP level
179+ for _ , tlpLevel := range testReq .Applicability {
180+ if data .TestSuites [tlpLevel ] == nil {
181+ data .TestSuites [tlpLevel ] = []string {}
182+ }
183+ data .TestSuites [tlpLevel ] = append (data .TestSuites [tlpLevel ], testReq .ID )
249184 }
250- data .TestSuites [tlpLevel ] = append (data .TestSuites [tlpLevel ], strings .ReplaceAll (testReq .ID , "." , "_" ))
251185 }
252186 }
253187 }
254188 return
255189}
256-
257- func readYAMLURL () (data ControlCatalog , err error ) {
258- resp , err := http .Get (SourcePath )
259- if err != nil {
260- logger .Error ("Failed to fetch URL: %v" , err )
261- return
262- }
263- defer resp .Body .Close ()
264-
265- if resp .StatusCode != http .StatusOK {
266- logger .Error ("Failed to fetch URL: %v" , resp .Status )
267- return
268- }
269-
270- decoder := yaml .NewDecoder (resp .Body )
271- err = decoder .Decode (& data )
272- if err != nil {
273- logger .Error ("Failed to decode YAML from URL: %v" , err )
274- return
275- }
276-
277- return
278- }
279-
280- func readYAMLFile () (data ControlCatalog , err error ) {
281- yamlFile , err := os .ReadFile (SourcePath )
282- if err != nil {
283- logger .Error (fmt .Sprintf ("Error reading local source file: %s (%v)" , SourcePath , err ))
284- return
285- }
286-
287- err = yaml .Unmarshal (yamlFile , & data )
288- if err != nil {
289- logger .Error ("Error unmarshalling YAML file: %v" , err )
290- return
291- }
292-
293- return
294- }
0 commit comments