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 }