-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c2161f9
commit 3fd87d1
Showing
3 changed files
with
313 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,40 @@ | ||
# extrahop-backup | ||
Store ExtraHop configuration backups in a Git repository | ||
|
||
Stores ExtraHop configuration backups in a Git repository. | ||
|
||
It currently grabs the ExtraHop configuration through the ExtraHop API, and it grabs the following endpoints: | ||
|
||
- runningconfig | ||
- triggers | ||
- auditlog | ||
|
||
It takes these endpoints, dumps the outputted data into a git repository, and commits the files. | ||
|
||
It runs on Windows or Linux, and should be scheduled by a cron job (or a scheduled task for Windows). | ||
|
||
It expects that the owner of the process that is invoking the commands has SSH access to the git repository. | ||
|
||
# Running | ||
|
||
Running `extrahop-backup -?` will output a list of flags: | ||
|
||
``` | ||
-apikey string | ||
Your API Key for the ExtraHop host. | ||
-gitdir string | ||
Optional. Directory to do a git clone into. (default "C:\\Users\\YourUser\\AppData\\Local\\Temp") | ||
-gitrepo string | ||
Git repository to store backups into. | ||
-host string | ||
URL to the ExtraHop host. E.g. https://extrahop01.example.com. | ||
-v Alias to -version. | ||
-verbose | ||
Output verbose details. | ||
-version | ||
Print version information. | ||
``` | ||
An example invocation: | ||
|
||
extrahop-backup -host "http://extrahop01.example.com" -apikey "2378ab76677100f78362acc7387382ab" -gitrepo "git@gitlab.example.com:configbackups/extrahop-config.git" | ||
|
||
Binary downloads can be found [on the release page](https://github.com/mhenderson-so/extrahop-backup/releases). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
param ( | ||
[switch]$Debug, | ||
[switch]$Linux | ||
) | ||
|
||
[string]$BinaryPath = "" | ||
[string]$GOOS = "" | ||
[string]$GOOSColour = "" | ||
|
||
if ($Debug){ | ||
if ($Linux){ | ||
$BinaryPath = "extrahop-backup.debug" | ||
} else { | ||
$BinaryPath = "extrahop-backup.debug.exe" | ||
} | ||
} else { | ||
if ($Linux){ | ||
$BinaryPath = "extrahop-backup" | ||
} else { | ||
$BinaryPath = "extrahop-backup.exe" | ||
} | ||
} | ||
|
||
if ($Linux){ | ||
$GOOS = "linux" | ||
$GOOSColour = "magenta" | ||
} else { | ||
$GOOS = "windows" | ||
$GOOSColour = "DarkCyan" | ||
} | ||
|
||
$env:GOOS = $GOOS | ||
|
||
if ($Debug){ | ||
Write-Host "---- Building Debug (" -NoNewLine | ||
} else { | ||
Write-Host "---- Building (" -NoNewLine | ||
} | ||
|
||
Write-Host $GOOS -f $GOOSColour -NoNewLine | ||
|
||
Write-Host ") ----" | ||
|
||
Write-Host "Removing existing $BinaryPath" | ||
Remove-Item $BinaryPath -Force -ErrorAction SilentlyContinue | ||
|
||
Write-Host "Building... $BinaryPath" | ||
|
||
& go generate | ||
if ($Debug){ | ||
& godebug build -o $BinaryPath | ||
} else { | ||
& go build -o $BinaryPath | ||
} | ||
|
||
if (-not (Test-Path $BinaryPath)) { | ||
Write-Host "Build Error" -f Red | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
var ( | ||
fEHHost = flag.String("host", "", "URL to the ExtraHop host. E.g. https://extrahop01.example.com.") | ||
fEHAPIKey = flag.String("apikey", "", "Your API Key for the ExtraHop host.") | ||
fGitDir = flag.String("gitdir", os.TempDir(), "Optional. Directory to do a git clone into.") | ||
fGitRepo = flag.String("gitrepo", "", "Git repository to store backups into.") | ||
fPrintV = flag.Bool("v", false, "Alias to -version.") | ||
fPrintVer = flag.Bool("version", false, "Print version information.") | ||
fVerbose = flag.Bool("verbose", false, "Output verbose details.") | ||
|
||
ver = "ExtraHop backup utility v0.0.1" | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
if *fPrintVer || *fPrintV { | ||
fmt.Println(ver) | ||
return | ||
} | ||
|
||
if *fEHHost == "" { | ||
fmt.Fprintln(os.Stderr, "ExtraHop -host has not been specified") | ||
return | ||
} | ||
|
||
if *fEHAPIKey == "" { | ||
fmt.Fprintln(os.Stderr, "ExtraHop -apikey has not been specified") | ||
return | ||
} | ||
|
||
if *fGitRepo == "" { | ||
fmt.Fprintln(os.Stderr, "Git -gitrepo has not been specified") | ||
return | ||
} | ||
|
||
//Create the temporary folder that the backup git repo will be checked out into. This will be deleted at | ||
//the end. A fresh checkout will be done every time. | ||
if *fGitDir == os.TempDir() { | ||
*fGitDir, _ = ioutil.TempDir("", "ExtraHopBackup") | ||
if *fVerbose { | ||
fmt.Println("Setting temporary git checkout directory to", *fGitDir) | ||
} | ||
} else { | ||
*fGitDir += "ExtraHopBackup" | ||
os.MkdirAll(*fGitDir, 0755) | ||
} | ||
|
||
//Clone the repo into our output directory | ||
gitClone(*fGitDir, *fGitRepo) | ||
|
||
//Create a client, so that we can reuse this client connection multiple times for requests | ||
client := &http.Client{} | ||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | ||
req.Header = via[0].Header | ||
return nil | ||
} | ||
|
||
// TODO (mhenderson) 2015-05-04: This could be sped up by throwing these into goroutines(). We have a common | ||
// http client that they can use to share the same connection. In fact, we could be fetching these | ||
// even while we are checking out the git repo. | ||
|
||
// TODO (mhenderson) 2015-05-04: Make this modular so you can specify which endpoints to backup from the command | ||
// line, or a config file, or something along those lines. | ||
|
||
//Grab the endpoints and put them into the git repo | ||
backupEHEndpoint("runningconfig", client) | ||
backupEHEndpoint("triggers", client) | ||
backupEHEndpoint("auditlog?limit=999999&offset=0", client) | ||
|
||
//Add every file in the git repo and push | ||
pushAll(*fGitDir, "master") | ||
|
||
//Delete the temporary folder | ||
if *fVerbose { | ||
fmt.Println("Cleaning up. Deleting ", *fGitDir) | ||
} | ||
|
||
dirErr := os.RemoveAll(*fGitDir) | ||
|
||
//Git for Windows goes and puts some read-only files in the directory, which Go can't delete by default. | ||
//So if we run into this, we can do an OS call to fix that right up. | ||
if dirErr != nil && runtime.GOOS == "windows" { | ||
cmd := exec.Command("cmd.exe", "/C", "rmdir", "/S", "/Q", *fGitDir) | ||
if *fVerbose { | ||
fmt.Println("Cleaning up. Can't delete:", dirErr) | ||
fmt.Println("Cleaning up. Invoking OS deletion:", cmd.Args) | ||
} | ||
cmd.Run() | ||
} | ||
} | ||
|
||
// backupEHEndpoint takes an ExtraHop API endpoint and dumps it into the Git repository | ||
func backupEHEndpoint(endpoint string, client *http.Client) error { | ||
ehBody, err := invokeEHAPI(endpoint, client) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
endpointName := strings.Split(endpoint, "?")[0] | ||
backupFile := path.Join(*fGitDir, fmt.Sprintf("%v.json", endpointName)) | ||
|
||
if *fVerbose { | ||
fmt.Println("Writing", endpoint, "to", backupFile) | ||
} | ||
|
||
err = ioutil.WriteFile(backupFile, ehBody, 0755) | ||
return err | ||
} | ||
|
||
// invokeEHAPI takes an ExtraHop API endpoint and returns some pretty-printed JSON of the returned | ||
// values. | ||
func invokeEHAPI(endpoint string, client *http.Client) ([]byte, error) { | ||
ehURL, err := url.Parse(fmt.Sprintf("%v/api/v1/%v", *fEHHost, endpoint)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req, _ := http.NewRequest("GET", ehURL.String(), nil) | ||
|
||
req.Header.Set("Accept", "application/json") | ||
req.Header.Set("User-Agent", "extrahop-backupclient(github.com/mhenderson-so/extrahop-backup)") | ||
req.Header.Set("Authorization", fmt.Sprintf("ExtraHop apikey=%v", *fEHAPIKey)) | ||
|
||
if *fVerbose { | ||
fmt.Println("Requesting", req.URL.String()) | ||
} | ||
res, err := client.Do(req) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
defer res.Body.Close() | ||
body, err := ioutil.ReadAll(res.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// http://stackoverflow.com/a/29046984/69683 | ||
var prettyJSON bytes.Buffer | ||
err = json.Indent(&prettyJSON, body, "", "\t") | ||
|
||
return prettyJSON.Bytes(), err | ||
} | ||
|
||
// setVerbose sets the output of exec.cmd processes to stdout | ||
func setVerbose(cmd *exec.Cmd) { | ||
if *fVerbose { | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
} | ||
} | ||
|
||
// gitClone does what it says on the tin. It clones a git repo to a directory. | ||
func gitClone(tmpDir, repoDir string) { | ||
// clone in a temporary rep | ||
cmd := exec.Command("git", "clone", *fGitRepo, tmpDir, "--depth=1", "--no-single-branch") // depth: speed up things | ||
//cmd.Dir = tmpDir | ||
setVerbose(cmd) | ||
if err := cmd.Run(); err != nil { | ||
panic(fmt.Sprintf("could not clone the repo: %v, %v", cmd, err)) | ||
} | ||
} | ||
|
||
// pushAll adds all the files in a repo, adds a default commit message, and pushes it. | ||
func pushAll(repoDir, branch string) { | ||
cmd := exec.Command("git", "add", ".") | ||
cmd.Dir = repoDir | ||
setVerbose(cmd) | ||
if err := cmd.Run(); err != nil { | ||
panic(fmt.Sprintf("could not add into git: %v", cmd)) | ||
} | ||
|
||
msg := fmt.Sprintf("autoCommit") | ||
// get Added and Changed files TODO | ||
// git ls-files --others --exclude-standar : added | ||
// git ls-files -m : changed | ||
|
||
cmd = exec.Command("git", "commit", "-m", msg) | ||
cmd.Dir = repoDir | ||
txt, err := cmd.CombinedOutput() | ||
if strings.Contains(string(txt), "nothing to commit") { | ||
fmt.Println("No changes detected, don't push") | ||
return | ||
} | ||
if err != nil { | ||
panic("could not read git commit output: " + err.Error()) | ||
} | ||
|
||
if *fVerbose { | ||
fmt.Printf(string(txt)) | ||
} | ||
cmd = exec.Command("git", "push", "--set-upstream", "origin", branch) | ||
cmd.Dir = repoDir | ||
setVerbose(cmd) | ||
if err = cmd.Run(); err != nil { | ||
panic(fmt.Sprintf("could not push: %v", cmd)) | ||
} | ||
} |