backup-restore.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "github.com/crowdsecurity/crowdsec/pkg/cwapi"
  12. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  13. "github.com/crowdsecurity/crowdsec/pkg/outputs"
  14. log "github.com/sirupsen/logrus"
  15. "github.com/spf13/cobra"
  16. )
  17. //it's a rip of the cli version, but in silent-mode
  18. func silenceInstallItem(name string, obtype string) (string, error) {
  19. for _, it := range cwhub.HubIdx[obtype] {
  20. if it.Name == name {
  21. if download_only && it.Downloaded && it.UpToDate {
  22. return fmt.Sprintf("%s is already downloaded and up-to-date", it.Name), nil
  23. }
  24. it, err := cwhub.DownloadLatest(it, cwhub.Hubdir, force_install)
  25. if err != nil {
  26. return "", fmt.Errorf("error while downloading %s : %v", it.Name, err)
  27. }
  28. cwhub.HubIdx[obtype][it.Name] = it
  29. if download_only {
  30. return fmt.Sprintf("Downloaded %s to %s", it.Name, cwhub.Hubdir+"/"+it.RemotePath), nil
  31. }
  32. it, err = cwhub.EnableItem(it, cwhub.Installdir, cwhub.Hubdir)
  33. if err != nil {
  34. return "", fmt.Errorf("error while enabled %s : %v", it.Name, err)
  35. }
  36. cwhub.HubIdx[obtype][it.Name] = it
  37. return fmt.Sprintf("Enabled %s", it.Name), nil
  38. }
  39. }
  40. return "", fmt.Errorf("%s not found in hub index", name)
  41. }
  42. /*help to copy the file, ioutil doesn't offer the feature*/
  43. func copyFileContents(src, dst string) (err error) {
  44. in, err := os.Open(src)
  45. if err != nil {
  46. return
  47. }
  48. defer in.Close()
  49. out, err := os.Create(dst)
  50. if err != nil {
  51. return
  52. }
  53. defer func() {
  54. cerr := out.Close()
  55. if err == nil {
  56. err = cerr
  57. }
  58. }()
  59. if _, err = io.Copy(out, in); err != nil {
  60. return
  61. }
  62. err = out.Sync()
  63. return
  64. }
  65. /*copy the file, ioutile doesn't offer the feature*/
  66. func copyFile(sourceSymLink, destinationFile string) (err error) {
  67. sourceFile, err := filepath.EvalSymlinks(sourceSymLink)
  68. if err != nil {
  69. log.Infof("Not a symlink : %s", err)
  70. sourceFile = sourceSymLink
  71. }
  72. sourceFileStat, err := os.Stat(sourceFile)
  73. if err != nil {
  74. return
  75. }
  76. if !sourceFileStat.Mode().IsRegular() {
  77. // cannot copy non-regular files (e.g., directories,
  78. // symlinks, devices, etc.)
  79. return fmt.Errorf("copyFile: non-regular source file %s (%q)", sourceFileStat.Name(), sourceFileStat.Mode().String())
  80. }
  81. destinationFileStat, err := os.Stat(destinationFile)
  82. if err != nil {
  83. if !os.IsNotExist(err) {
  84. return
  85. }
  86. } else {
  87. if !(destinationFileStat.Mode().IsRegular()) {
  88. return fmt.Errorf("copyFile: non-regular destination file %s (%q)", destinationFileStat.Name(), destinationFileStat.Mode().String())
  89. }
  90. if os.SameFile(sourceFileStat, destinationFileStat) {
  91. return
  92. }
  93. }
  94. if err = os.Link(sourceFile, destinationFile); err == nil {
  95. return
  96. }
  97. err = copyFileContents(sourceFile, destinationFile)
  98. return
  99. }
  100. /*given a backup directory, restore configs (parser,collections..) both tainted and untainted.
  101. as well attempts to restore api credentials after verifying the existing ones aren't good
  102. finally restores the acquis.yaml file*/
  103. func restoreFromDirectory(source string) error {
  104. var err error
  105. /*backup scenarios etc.*/
  106. for _, itype := range cwhub.ItemTypes {
  107. itemDirectory := fmt.Sprintf("%s/%s/", source, itype)
  108. if _, err = os.Stat(itemDirectory); err != nil {
  109. log.Infof("no %s in backup", itype)
  110. continue
  111. }
  112. /*restore the upstream items*/
  113. upstreamListFN := fmt.Sprintf("%s/upstream-%s.json", itemDirectory, itype)
  114. file, err := ioutil.ReadFile(upstreamListFN)
  115. if err != nil {
  116. return fmt.Errorf("error while opening %s : %s", upstreamListFN, err)
  117. }
  118. var upstreamList []string
  119. err = json.Unmarshal([]byte(file), &upstreamList)
  120. if err != nil {
  121. return fmt.Errorf("error unmarshaling %s : %s", upstreamListFN, err)
  122. }
  123. for _, toinstall := range upstreamList {
  124. label, err := silenceInstallItem(toinstall, itype)
  125. if err != nil {
  126. log.Errorf("Error while installing %s : %s", toinstall, err)
  127. } else if label != "" {
  128. log.Infof("Installed %s : %s", toinstall, label)
  129. } else {
  130. log.Printf("Installed %s : ok", toinstall)
  131. }
  132. }
  133. /*restore the local and tainted items*/
  134. files, err := ioutil.ReadDir(itemDirectory)
  135. if err != nil {
  136. return fmt.Errorf("failed enumerating files of %s : %s", itemDirectory, err)
  137. }
  138. for _, file := range files {
  139. //dir are stages, keep track
  140. if !file.IsDir() {
  141. continue
  142. }
  143. stage := file.Name()
  144. stagedir := fmt.Sprintf("%s/%s/%s/", config.InstallFolder, itype, stage)
  145. log.Debugf("Found stage %s in %s, target directory : %s", stage, itype, stagedir)
  146. if err = os.MkdirAll(stagedir, os.ModePerm); err != nil {
  147. return fmt.Errorf("error while creating stage directory %s : %s", stagedir, err)
  148. }
  149. /*find items*/
  150. ifiles, err := ioutil.ReadDir(itemDirectory + "/" + stage + "/")
  151. if err != nil {
  152. return fmt.Errorf("failed enumerating files of %s : %s", itemDirectory+"/"+stage, err)
  153. }
  154. //finaly copy item
  155. for _, tfile := range ifiles {
  156. log.Infof("Going to restore local/tainted [%s]", tfile.Name())
  157. sourceFile := fmt.Sprintf("%s/%s/%s", itemDirectory, stage, tfile.Name())
  158. destinationFile := fmt.Sprintf("%s%s", stagedir, tfile.Name())
  159. if err = copyFile(sourceFile, destinationFile); err != nil {
  160. return fmt.Errorf("failed copy %s %s to %s : %s", itype, sourceFile, destinationFile, err)
  161. } else {
  162. log.Infof("restored %s to %s", sourceFile, destinationFile)
  163. }
  164. }
  165. }
  166. }
  167. /*restore api credentials*/
  168. //check if credentials exists :
  169. // - if no, restore
  170. // - if yes, try them :
  171. // - if it works, left untouched
  172. // - if not, restore
  173. // -> try login
  174. if err := restoreAPICreds(source); err != nil {
  175. return fmt.Errorf("failed to restore api credentials : %s", err)
  176. }
  177. /*
  178. Restore acquis
  179. */
  180. yamlAcquisFile := fmt.Sprintf("%s/acquis.yaml", config.InstallFolder)
  181. bac := fmt.Sprintf("%s/acquis.yaml", source)
  182. if err = copyFile(bac, yamlAcquisFile); err != nil {
  183. return fmt.Errorf("failed copy %s to %s : %s", bac, yamlAcquisFile, err)
  184. }
  185. log.Infof("Restore acquis to %s", yamlAcquisFile)
  186. return nil
  187. }
  188. func restoreAPICreds(source string) error {
  189. var err error
  190. /*check existing configuration*/
  191. apiyaml := path.Join(config.InstallFolder, apiConfigFile)
  192. api := &cwapi.ApiCtx{}
  193. if err = api.LoadConfig(apiyaml); err != nil {
  194. return fmt.Errorf("unable to load api config %s : %s", apiyaml, err)
  195. }
  196. if api.Creds.User != "" {
  197. log.Infof("Credentials present in existing configuration, try before override")
  198. err := api.Signin()
  199. if err == nil {
  200. log.Infof("Credentials present allow authentication, don't override !")
  201. return nil
  202. } else {
  203. log.Infof("Credentials aren't valid : %s", err)
  204. }
  205. }
  206. /*existing config isn't good, override it !*/
  207. ret, err := ioutil.ReadFile(path.Join(source, "api_creds.json"))
  208. if err != nil {
  209. return fmt.Errorf("failed to read api creds from save : %s", err)
  210. }
  211. if err := json.Unmarshal(ret, &api.Creds); err != nil {
  212. return fmt.Errorf("failed unmarshaling saved credentials : %s", err)
  213. }
  214. api.CfgUser = api.Creds.User
  215. api.CfgPassword = api.Creds.Password
  216. /*override the existing yaml file*/
  217. if err := api.WriteConfig(apiyaml); err != nil {
  218. return fmt.Errorf("failed writing to %s : %s", apiyaml, err)
  219. } else {
  220. log.Infof("Overwritting %s with backup info", apiyaml)
  221. }
  222. /*reload to check everything is safe*/
  223. if err = api.LoadConfig(apiyaml); err != nil {
  224. return fmt.Errorf("unable to load api config %s : %s", apiyaml, err)
  225. }
  226. if err := api.Signin(); err != nil {
  227. log.Errorf("Failed to authenticate after credentials restaurtion : %v", err)
  228. } else {
  229. log.Infof("Successfully auth to API after credentials restauration")
  230. }
  231. return nil
  232. }
  233. func backupToDirectory(target string) error {
  234. var itemDirectory string
  235. var upstreamParsers []string
  236. var err error
  237. if target == "" {
  238. return fmt.Errorf("target directory can't be empty")
  239. }
  240. log.Warningf("Starting configuration backup")
  241. _, err = os.Stat(target)
  242. if err == nil {
  243. return fmt.Errorf("%s already exists", target)
  244. }
  245. if err = os.MkdirAll(target, os.ModePerm); err != nil {
  246. return fmt.Errorf("error while creating %s : %s", target, err)
  247. }
  248. /*
  249. backup configurations :
  250. - parers, scenarios, collections, postoverflows
  251. */
  252. for _, itemType := range cwhub.ItemTypes {
  253. clog := log.WithFields(log.Fields{
  254. "type": itemType,
  255. })
  256. if _, ok := cwhub.HubIdx[itemType]; ok {
  257. itemDirectory = fmt.Sprintf("%s/%s/", target, itemType)
  258. if err := os.MkdirAll(itemDirectory, os.ModePerm); err != nil {
  259. return fmt.Errorf("error while creating %s : %s", itemDirectory, err)
  260. }
  261. upstreamParsers = []string{}
  262. stage := ""
  263. for k, v := range cwhub.HubIdx[itemType] {
  264. clog = clog.WithFields(log.Fields{
  265. "file": v.Name,
  266. })
  267. if !v.Installed { //only backup installed ones
  268. clog.Debugf("[%s] : not installed", k)
  269. continue
  270. }
  271. //for the local/tainted ones, we backup the full file
  272. if v.Tainted || v.Local || !v.UpToDate {
  273. //we need to backup stages for parsers
  274. if itemType == cwhub.PARSERS || itemType == cwhub.PARSERS_OVFLW {
  275. tmp := strings.Split(v.LocalPath, "/")
  276. stage = "/" + tmp[len(tmp)-2] + "/"
  277. fstagedir := fmt.Sprintf("%s%s", itemDirectory, stage)
  278. if err := os.MkdirAll(fstagedir, os.ModePerm); err != nil {
  279. return fmt.Errorf("error while creating stage dir %s : %s", fstagedir, err)
  280. }
  281. }
  282. clog.Debugf("[%s] : backuping file (tainted:%t local:%t up-to-date:%t)", k, v.Tainted, v.Local, v.UpToDate)
  283. tfile := fmt.Sprintf("%s%s%s", itemDirectory, stage, v.FileName)
  284. //clog.Infof("item : %s", spew.Sdump(v))
  285. if err = copyFile(v.LocalPath, tfile); err != nil {
  286. return fmt.Errorf("failed copy %s %s to %s : %s", itemType, v.LocalPath, tfile, err)
  287. }
  288. clog.Infof("local/tainted saved %s to %s", v.LocalPath, tfile)
  289. continue
  290. }
  291. clog.Debugf("[%s] : from hub, just backup name (up-to-date:%t)", k, v.UpToDate)
  292. clog.Infof("saving, version:%s, up-to-date:%t", v.Version, v.UpToDate)
  293. upstreamParsers = append(upstreamParsers, v.Name)
  294. }
  295. //write the upstream items
  296. upstreamParsersFname := fmt.Sprintf("%s/upstream-%s.json", itemDirectory, itemType)
  297. upstreamParsersContent, err := json.MarshalIndent(upstreamParsers, "", " ")
  298. if err != nil {
  299. return fmt.Errorf("failed marshaling upstream parsers : %s", err)
  300. }
  301. err = ioutil.WriteFile(upstreamParsersFname, upstreamParsersContent, 0644)
  302. if err != nil {
  303. return fmt.Errorf("unable to write to %s %s : %s", itemType, upstreamParsersFname, err)
  304. }
  305. clog.Infof("Wrote %d entries for %s to %s", len(upstreamParsers), itemType, upstreamParsersFname)
  306. } else {
  307. clog.Infof("No %s to backup.", itemType)
  308. }
  309. }
  310. /*
  311. Backup acquis
  312. */
  313. yamlAcquisFile := fmt.Sprintf("%s/acquis.yaml", config.InstallFolder)
  314. bac := fmt.Sprintf("%s/acquis.yaml", target)
  315. if err = copyFile(yamlAcquisFile, bac); err != nil {
  316. return fmt.Errorf("failed copy %s to %s : %s", yamlAcquisFile, bac, err)
  317. }
  318. log.Infof("Saved acquis to %s", bac)
  319. /*
  320. Backup default.yaml
  321. */
  322. defyaml := fmt.Sprintf("%s/default.yaml", config.InstallFolder)
  323. bac = fmt.Sprintf("%s/default.yaml", target)
  324. if err = copyFile(defyaml, bac); err != nil {
  325. return fmt.Errorf("failed copy %s to %s : %s", yamlAcquisFile, bac, err)
  326. }
  327. log.Infof("Saved default yaml to %s", bac)
  328. /*
  329. Backup API info
  330. */
  331. if outputCTX == nil {
  332. log.Fatalf("no API output context, won't save api credentials")
  333. }
  334. outputCTX.API = &cwapi.ApiCtx{}
  335. if err = outputCTX.API.LoadConfig(path.Join(config.InstallFolder, apiConfigFile)); err != nil {
  336. return fmt.Errorf("unable to load api config %s : %s", path.Join(config.InstallFolder, apiConfigFile), err)
  337. }
  338. credsYaml, err := json.Marshal(&outputCTX.API.Creds)
  339. if err != nil {
  340. log.Fatalf("can't marshal credentials : %v", err)
  341. }
  342. apiCredsDumped := fmt.Sprintf("%s/api_creds.json", target)
  343. err = ioutil.WriteFile(apiCredsDumped, credsYaml, 0600)
  344. if err != nil {
  345. return fmt.Errorf("unable to write credentials to %s : %s", apiCredsDumped, err)
  346. }
  347. log.Infof("Saved configuration to %s", target)
  348. return nil
  349. }
  350. func NewBackupCmd() *cobra.Command {
  351. var cmdBackup = &cobra.Command{
  352. Use: "backup [save|restore] <directory>",
  353. Short: "Backup or restore configuration (api, parsers, scenarios etc.) to/from directory",
  354. Long: `This command is here to help you save and/or restore crowdsec configurations to simple replication`,
  355. Example: `cscli backup save ./my-backup
  356. cscli backup restore ./my-backup`,
  357. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  358. if !config.configured {
  359. return fmt.Errorf("you must configure cli before interacting with hub")
  360. }
  361. return nil
  362. },
  363. }
  364. var cmdBackupSave = &cobra.Command{
  365. Use: "save <directory>",
  366. Short: "Backup configuration (api, parsers, scenarios etc.) to directory",
  367. Long: `backup command will try to save all relevant informations to crowdsec config, including :
  368. - List of scenarios, parsers, postoverflows and collections that are up-to-date
  369. - Actual backup of tainted/local/out-of-date scenarios, parsers, postoverflows and collections
  370. - Backup of API credentials
  371. - Backup of acqusition configuration
  372. `,
  373. Example: `cscli backup save ./my-backup`,
  374. Args: cobra.ExactArgs(1),
  375. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  376. if !config.configured {
  377. return fmt.Errorf("you must configure cli before interacting with hub")
  378. }
  379. return nil
  380. },
  381. Run: func(cmd *cobra.Command, args []string) {
  382. var err error
  383. outputConfig := outputs.OutputFactory{
  384. BackendFolder: config.BackendPluginFolder,
  385. }
  386. outputCTX, err = outputs.NewOutput(&outputConfig, false)
  387. if err != nil {
  388. log.Fatalf("Failed to load output plugins")
  389. }
  390. if err := cwhub.GetHubIdx(); err != nil {
  391. log.Fatalf("Failed to get Hub index : %v", err)
  392. }
  393. if err := backupToDirectory(args[0]); err != nil {
  394. log.Fatalf("Failed backuping to %s : %s", args[0], err)
  395. }
  396. },
  397. }
  398. cmdBackup.AddCommand(cmdBackupSave)
  399. var cmdBackupRestore = &cobra.Command{
  400. Use: "restore <directory>",
  401. Short: "Restore configuration (api, parsers, scenarios etc.) from directory",
  402. Long: `restore command will try to restore all saved information from <directory> to yor local setup, including :
  403. - Installation of up-to-date scenarios/parsers/... via cscli
  404. - Restauration of tainted/local/out-of-date scenarios/parsers/... file
  405. - Restauration of API credentials (if the existing ones aren't working)
  406. - Restauration of acqusition configuration
  407. `,
  408. Example: `cscli backup restore ./my-backup`,
  409. Args: cobra.ExactArgs(1),
  410. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  411. if !config.configured {
  412. return fmt.Errorf("you must configure cli before interacting with hub")
  413. }
  414. return nil
  415. },
  416. Run: func(cmd *cobra.Command, args []string) {
  417. var err error
  418. outputConfig := outputs.OutputFactory{
  419. BackendFolder: config.BackendPluginFolder,
  420. }
  421. outputCTX, err = outputs.NewOutput(&outputConfig, false)
  422. if err != nil {
  423. log.Fatalf("Failed to load output plugins")
  424. }
  425. if err := cwhub.GetHubIdx(); err != nil {
  426. log.Fatalf("failed to get Hub index : %v", err)
  427. }
  428. if err := restoreFromDirectory(args[0]); err != nil {
  429. log.Fatalf("failed restoring from %s : %s", args[0], err)
  430. }
  431. },
  432. }
  433. cmdBackup.AddCommand(cmdBackupRestore)
  434. return cmdBackup
  435. }