bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/cwhub/loader.go (about)

     1  package cwhub
     2  
     3  import (
     4  	"encoding/json"
     5  	//"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"sort"
     9  
    10  	"github.com/pkg/errors"
    11  	"golang.org/x/mod/semver"
    12  
    13  	//"log"
    14  
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"bitbucket.org/Aishee/synsec/pkg/csconfig"
    20  	log "github.com/sirupsen/logrus"
    21  )
    22  
    23  /*the walk/parser_visit function can't receive extra args*/
    24  var hubdir, installdir, indexpath string
    25  
    26  func parser_visit(path string, f os.FileInfo, err error) error {
    27  
    28  	var target Item
    29  	var local bool
    30  	var hubpath string
    31  	var inhub bool
    32  	var fname string
    33  	var ftype string
    34  	var fauthor string
    35  	var stage string
    36  
    37  	path, err = filepath.Abs(path)
    38  	if err != nil {
    39  		return err
    40  	}
    41  	//we only care about files
    42  	if f == nil || f.IsDir() {
    43  		return nil
    44  	}
    45  	//we only care about yaml files
    46  	if !strings.HasSuffix(f.Name(), ".yaml") && !strings.HasSuffix(f.Name(), ".yml") {
    47  		return nil
    48  	}
    49  
    50  	subs := strings.Split(path, "/")
    51  
    52  	log.Tracef("path:%s, hubdir:%s, installdir:%s", path, hubdir, installdir)
    53  	/*we're in hub (~/.hub/hub/)*/
    54  	if strings.HasPrefix(path, hubdir) {
    55  		log.Tracef("in hub dir")
    56  		inhub = true
    57  		//.../hub/parsers/s00-raw/synsec/skip-pretag.yaml
    58  		//.../hub/scenarios/synsec/ssh_bf.yaml
    59  		//.../hub/profiles/synsec/linux.yaml
    60  		if len(subs) < 4 {
    61  			log.Fatalf("path is too short : %s (%d)", path, len(subs))
    62  		}
    63  		fname = subs[len(subs)-1]
    64  		fauthor = subs[len(subs)-2]
    65  		stage = subs[len(subs)-3]
    66  		ftype = subs[len(subs)-4]
    67  	} else if strings.HasPrefix(path, installdir) { /*we're in install /etc/synsec/<type>/... */
    68  		log.Tracef("in install dir")
    69  		if len(subs) < 3 {
    70  			log.Fatalf("path is too short : %s (%d)", path, len(subs))
    71  		}
    72  		///.../config/parser/stage/file.yaml
    73  		///.../config/postoverflow/stage/file.yaml
    74  		///.../config/scenarios/scenar.yaml
    75  		///.../config/collections/linux.yaml //file is empty
    76  		fname = subs[len(subs)-1]
    77  		stage = subs[len(subs)-2]
    78  		ftype = subs[len(subs)-3]
    79  		fauthor = ""
    80  	} else {
    81  		return fmt.Errorf("File '%s' is not from hub '%s' nor from the configuration directory '%s'", path, hubdir, installdir)
    82  	}
    83  
    84  	//log.Printf("%s -> name:%s stage:%s", path, fname, stage)
    85  	if stage == SCENARIOS {
    86  		ftype = SCENARIOS
    87  		stage = ""
    88  	} else if stage == COLLECTIONS {
    89  		ftype = COLLECTIONS
    90  		stage = ""
    91  	} else if ftype != PARSERS && ftype != PARSERS_OVFLW /*its a PARSER / PARSER_OVFLW with a stage */ {
    92  		return fmt.Errorf("unknown configuration type for file '%s'", path)
    93  	}
    94  
    95  	log.Tracef("CORRECTED [%s] by [%s] in stage [%s] of type [%s]", fname, fauthor, stage, ftype)
    96  
    97  	/*
    98  		we can encounter 'collections' in the form of a symlink :
    99  		/etc/synsec/.../collections/linux.yaml -> ~/.hub/hub/collections/.../linux.yaml
   100  		when the collection is installed, both files are created
   101  	*/
   102  	//non symlinks are local user files or hub files
   103  	if f.Mode()&os.ModeSymlink == 0 {
   104  		local = true
   105  		log.Tracef("%s isn't a symlink", path)
   106  	} else {
   107  		hubpath, err = os.Readlink(path)
   108  		if err != nil {
   109  			return fmt.Errorf("unable to read symlink of %s", path)
   110  		}
   111  		//the symlink target doesn't exist, user might have remove ~/.hub/hub/...yaml without deleting /etc/synsec/....yaml
   112  		_, err := os.Lstat(hubpath)
   113  		if os.IsNotExist(err) {
   114  			log.Infof("%s is a symlink to %s that doesn't exist, deleting symlink", path, hubpath)
   115  			//remove the symlink
   116  			if err = os.Remove(path); err != nil {
   117  				return fmt.Errorf("failed to unlink %s: %+v", path, err)
   118  			}
   119  			return nil
   120  		}
   121  		log.Tracef("%s points to %s", path, hubpath)
   122  	}
   123  
   124  	//if it's not a symlink and not in hub, it's a local file, don't bother
   125  	if local && !inhub {
   126  		log.Tracef("%s is a local file, skip", path)
   127  		skippedLocal++
   128  		//	log.Printf("local scenario, skip.")
   129  		target.Name = fname
   130  		target.Stage = stage
   131  		target.Installed = true
   132  		target.Type = ftype
   133  		target.Local = true
   134  		target.LocalPath = path
   135  		target.UpToDate = true
   136  		x := strings.Split(path, "/")
   137  		target.FileName = x[len(x)-1]
   138  
   139  		hubIdx[ftype][fname] = target
   140  		return nil
   141  	}
   142  	//try to find which configuration item it is
   143  	log.Tracef("check [%s] of %s", fname, ftype)
   144  
   145  	match := false
   146  	for k, v := range hubIdx[ftype] {
   147  		log.Tracef("check [%s] vs [%s] : %s", fname, v.RemotePath, ftype+"/"+stage+"/"+fname+".yaml")
   148  		if fname != v.FileName {
   149  			log.Tracef("%s != %s (filename)", fname, v.FileName)
   150  			continue
   151  		}
   152  		//wrong stage
   153  		if v.Stage != stage {
   154  			continue
   155  		}
   156  		/*if we are walking hub dir, just mark present files as downloaded*/
   157  		if inhub {
   158  			//wrong author
   159  			if fauthor != v.Author {
   160  				continue
   161  			}
   162  			//wrong file
   163  			if v.Name+".yaml" != fauthor+"/"+fname {
   164  				continue
   165  			}
   166  			if path == hubdir+"/"+v.RemotePath {
   167  				log.Tracef("marking %s as downloaded", v.Name)
   168  				v.Downloaded = true
   169  			}
   170  		} else {
   171  			//wrong file
   172  			//<type>/<stage>/<author>/<name>.yaml
   173  			if !strings.HasSuffix(hubpath, v.RemotePath) {
   174  				//log.Printf("wrong file %s %s", hubpath, spew.Sdump(v))
   175  
   176  				continue
   177  			}
   178  		}
   179  		sha, err := getSHA256(path)
   180  		if err != nil {
   181  			log.Fatalf("Failed to get sha of %s : %v", path, err)
   182  		}
   183  		//let's reverse sort the versions to deal with hash collisions (#154)
   184  		versions := make([]string, 0, len(v.Versions))
   185  		for k := range v.Versions {
   186  			versions = append(versions, k)
   187  		}
   188  		sort.Sort(sort.Reverse(sort.StringSlice(versions)))
   189  
   190  		for _, version := range versions {
   191  			val := v.Versions[version]
   192  			if sha != val.Digest {
   193  				//log.Printf("matching filenames, wrong hash %s != %s -- %s", sha, val.Digest, spew.Sdump(v))
   194  				continue
   195  			} else {
   196  				/*we got an exact match, update struct*/
   197  				if !inhub {
   198  					log.Tracef("found exact match for %s, version is %s, latest is %s", v.Name, version, v.Version)
   199  					v.LocalPath = path
   200  					v.LocalVersion = version
   201  					v.Tainted = false
   202  					v.Downloaded = true
   203  					/*if we're walking the hub, present file doesn't means installed file*/
   204  					v.Installed = true
   205  					v.LocalHash = sha
   206  					x := strings.Split(path, "/")
   207  					target.FileName = x[len(x)-1]
   208  				}
   209  				if version == v.Version {
   210  					log.Tracef("%s is up-to-date", v.Name)
   211  					v.UpToDate = true
   212  				}
   213  				match = true
   214  				break
   215  			}
   216  		}
   217  		if !match {
   218  			log.Tracef("got tainted match for %s : %s", v.Name, path)
   219  			skippedTainted += 1
   220  			//the file and the stage is right, but the hash is wrong, it has been tainted by user
   221  			if !inhub {
   222  				v.LocalPath = path
   223  				v.Installed = true
   224  			}
   225  			v.UpToDate = false
   226  			v.LocalVersion = "?"
   227  			v.Tainted = true
   228  			v.LocalHash = sha
   229  			x := strings.Split(path, "/")
   230  			target.FileName = x[len(x)-1]
   231  
   232  		}
   233  		//update the entry if appropriate
   234  		if _, ok := hubIdx[ftype][k]; !ok {
   235  			hubIdx[ftype][k] = v
   236  		} else if !inhub {
   237  			hubIdx[ftype][k] = v
   238  		}
   239  		return nil
   240  	}
   241  	log.Infof("Ignoring file %s of type %s", path, ftype)
   242  	return nil
   243  }
   244  
   245  func CollecDepsCheck(v *Item) error {
   246  
   247  	if GetVersionStatus(v) != 0 { //not up-to-date
   248  		log.Debugf("%s dependencies not checked : not up-to-date", v.Name)
   249  		return nil
   250  	}
   251  
   252  	/*if it's a collection, ensure all the items are installed, or tag it as tainted*/
   253  	if v.Type == COLLECTIONS {
   254  		log.Tracef("checking submembers of %s installed:%t", v.Name, v.Installed)
   255  		var tmp = [][]string{v.Parsers, v.PostOverflows, v.Scenarios, v.Collections}
   256  		for idx, ptr := range tmp {
   257  			ptrtype := ItemTypes[idx]
   258  			for _, p := range ptr {
   259  				if val, ok := hubIdx[ptrtype][p]; ok {
   260  					log.Tracef("check %s installed:%t", val.Name, val.Installed)
   261  					if !v.Installed {
   262  						continue
   263  					}
   264  					if val.Type == COLLECTIONS {
   265  						log.Tracef("collec, recurse.")
   266  						if err := CollecDepsCheck(&val); err != nil {
   267  							return fmt.Errorf("sub collection %s is broken : %s", val.Name, err)
   268  						}
   269  						hubIdx[ptrtype][p] = val
   270  					}
   271  
   272  					//propagate the state of sub-items to set
   273  					if val.Tainted {
   274  						v.Tainted = true
   275  						return fmt.Errorf("tainted %s %s, tainted.", ptrtype, p)
   276  					} else if !val.Installed && v.Installed {
   277  						v.Tainted = true
   278  						return fmt.Errorf("missing %s %s, tainted.", ptrtype, p)
   279  					} else if !val.UpToDate {
   280  						v.UpToDate = false
   281  						return fmt.Errorf("outdated %s %s", ptrtype, p)
   282  					}
   283  					skip := false
   284  					for idx := range val.BelongsToCollections {
   285  						if val.BelongsToCollections[idx] == v.Name {
   286  							skip = true
   287  						}
   288  					}
   289  					if !skip {
   290  						val.BelongsToCollections = append(val.BelongsToCollections, v.Name)
   291  					}
   292  					hubIdx[ptrtype][p] = val
   293  					log.Tracef("checking for %s - tainted:%t uptodate:%t", p, v.Tainted, v.UpToDate)
   294  				} else {
   295  					log.Fatalf("Referred %s %s in collection %s doesn't exist.", ptrtype, p, v.Name)
   296  				}
   297  			}
   298  		}
   299  	}
   300  	return nil
   301  }
   302  
   303  func SyncDir(hub *csconfig.Hub, dir string) (error, []string) {
   304  	hubdir = hub.HubDir
   305  	installdir = hub.ConfigDir
   306  	indexpath = hub.HubIndexFile
   307  	warnings := []string{}
   308  
   309  	/*For each, scan PARSERS, PARSERS_OVFLW, SCENARIOS and COLLECTIONS last*/
   310  	for _, scan := range ItemTypes {
   311  		cpath, err := filepath.Abs(fmt.Sprintf("%s/%s", dir, scan))
   312  		if err != nil {
   313  			log.Errorf("failed %s : %s", cpath, err)
   314  		}
   315  		err = filepath.Walk(cpath, parser_visit)
   316  		if err != nil {
   317  			return err, warnings
   318  		}
   319  
   320  	}
   321  
   322  	for k, v := range hubIdx[COLLECTIONS] {
   323  		if v.Installed {
   324  			versStat := GetVersionStatus(&v)
   325  			if versStat == 0 { //latest
   326  				if err := CollecDepsCheck(&v); err != nil {
   327  					warnings = append(warnings, fmt.Sprintf("dependency of %s : %s", v.Name, err))
   328  					hubIdx[COLLECTIONS][k] = v
   329  				}
   330  			} else if versStat == 1 { //not up-to-date
   331  				warnings = append(warnings, fmt.Sprintf("update for collection %s available (currently:%s, latest:%s)", v.Name, v.LocalVersion, v.Version))
   332  			} else { //version is higher than the highest available from hub?
   333  				warnings = append(warnings, fmt.Sprintf("collection %s is in the future (currently:%s, latest:%s)", v.Name, v.LocalVersion, v.Version))
   334  			}
   335  			log.Debugf("installed (%s) - status:%d | installed:%s | latest : %s | full : %+v", v.Name, semver.Compare("v"+v.Version, "v"+v.LocalVersion), v.LocalVersion, v.Version, v.Versions)
   336  		}
   337  	}
   338  	return nil, warnings
   339  }
   340  
   341  /* Updates the infos from HubInit() with the local state */
   342  func LocalSync(hub *csconfig.Hub) (error, []string) {
   343  	skippedLocal = 0
   344  	skippedTainted = 0
   345  
   346  	err, warnings := SyncDir(hub, hub.ConfigDir)
   347  	if err != nil {
   348  		return fmt.Errorf("failed to scan %s : %s", hub.ConfigDir, err), warnings
   349  	}
   350  	err, _ = SyncDir(hub, hub.HubDir)
   351  	if err != nil {
   352  		return fmt.Errorf("failed to scan %s : %s", hub.HubDir, err), warnings
   353  	}
   354  	return nil, warnings
   355  }
   356  
   357  func GetHubIdx(hub *csconfig.Hub) error {
   358  	if hub == nil {
   359  		return fmt.Errorf("no configuration found for hub")
   360  	}
   361  	log.Debugf("loading hub idx %s", hub.HubIndexFile)
   362  	bidx, err := ioutil.ReadFile(hub.HubIndexFile)
   363  	if err != nil {
   364  		return errors.Wrap(err, "unable to read index file")
   365  	}
   366  	ret, err := LoadPkgIndex(bidx)
   367  	if err != nil {
   368  		if !errors.Is(err, ReferenceMissingError) {
   369  			log.Fatalf("Unable to load existing index : %v.", err)
   370  		}
   371  		return err
   372  	}
   373  	hubIdx = ret
   374  	err, _ = LocalSync(hub)
   375  	if err != nil {
   376  		log.Fatalf("Failed to sync Hub index with local deployment : %v", err)
   377  	}
   378  	return nil
   379  }
   380  
   381  /*LoadPkgIndex loads a local .index.json file and returns the map of parsers/scenarios/collections associated*/
   382  func LoadPkgIndex(buff []byte) (map[string]map[string]Item, error) {
   383  	var err error
   384  	var RawIndex map[string]map[string]Item
   385  	var missingItems []string
   386  
   387  	if err = json.Unmarshal(buff, &RawIndex); err != nil {
   388  		return nil, fmt.Errorf("failed to unmarshal index : %v", err)
   389  	}
   390  
   391  	log.Debugf("%d item types in hub index", len(ItemTypes))
   392  	/*Iterate over the different types to complete struct */
   393  	for _, itemType := range ItemTypes {
   394  		/*complete struct*/
   395  		log.Tracef("%d item", len(RawIndex[itemType]))
   396  		for idx, item := range RawIndex[itemType] {
   397  			item.Name = idx
   398  			item.Type = itemType
   399  			x := strings.Split(item.RemotePath, "/")
   400  			item.FileName = x[len(x)-1]
   401  			RawIndex[itemType][idx] = item
   402  			/*if it's a collection, check its sub-items are present*/
   403  			//XX should be done later
   404  			if itemType == COLLECTIONS {
   405  				var tmp = [][]string{item.Parsers, item.PostOverflows, item.Scenarios, item.Collections}
   406  				for idx, ptr := range tmp {
   407  					ptrtype := ItemTypes[idx]
   408  					for _, p := range ptr {
   409  						if _, ok := RawIndex[ptrtype][p]; !ok {
   410  							log.Errorf("Referred %s %s in collection %s doesn't exist.", ptrtype, p, item.Name)
   411  							missingItems = append(missingItems, p)
   412  						}
   413  					}
   414  				}
   415  			}
   416  		}
   417  	}
   418  	if len(missingItems) > 0 {
   419  		return RawIndex, fmt.Errorf("%q : %w", missingItems, ReferenceMissingError)
   420  	}
   421  
   422  	return RawIndex, nil
   423  }