diff --git a/api/query.go b/api/query.go index 81526ea00a6..34d7f3190ab 100644 --- a/api/query.go +++ b/api/query.go @@ -213,6 +213,12 @@ type logWriter struct { } func (self *logWriter) Write(b []byte) (int, error) { + select { + case <-self.ctx.Done(): + return 0, io.EOF + default: + } + select { case <-self.ctx.Done(): return 0, io.EOF diff --git a/artifacts/definitions/Server/Internal/ArtifactDescription.yaml b/artifacts/definitions/Server/Internal/ArtifactDescription.yaml index a162e616e2f..ca23a16ceef 100644 --- a/artifacts/definitions/Server/Internal/ArtifactDescription.yaml +++ b/artifacts/definitions/Server/Internal/ArtifactDescription.yaml @@ -43,7 +43,7 @@ reports: ### Tools {{ range $artifact.Tools -}} - * + * {{ end }} {{ end }} diff --git a/artifacts/definitions/Server/Utils/CreateCollector.yaml b/artifacts/definitions/Server/Utils/CreateCollector.yaml index 066e2ae318e..2ec0b347b26 100644 --- a/artifacts/definitions/Server/Utils/CreateCollector.yaml +++ b/artifacts/definitions/Server/Utils/CreateCollector.yaml @@ -144,6 +144,16 @@ parameters: because the packed builtin artifacts are only compatible with the current release version. + - name: opt_delete_at_exit + type: bool + default: N + description: | + If specified the collection will be deleted at exit. This only + makes sense when uploading to the cloud or a remote + location. NOTE: There is no way to check that the upload + actually worked so this flag deletes the collection regardless + of upload success. + - name: StandardCollection type: hidden default: | @@ -267,6 +277,12 @@ parameters: // The log is always written to the executable path LET log_filename <= pathspec(parse= baseline[0].Exe + ".log") + -- Remove the zip file and log file when done if the user asked for it. + LET _ <= if(condition=DeleteOnExit, then=atexit(query={ + SELECT rm(filename=zip_filename), rm(filename=log_filename) FROM scope() + WHERE log(message="Removed Zip file %v", args=zip_filename) + }, env=dict(zip_filename=zip_filename, log_filename=log_filename))) + -- Make a random hex string as a random password LET RandomPassword <= SELECT format(format="%02x", args=rand(range=255)) AS A @@ -470,6 +486,7 @@ sources: dict(name="ProgressTimeout", type="int", default=opt_progress_timeout), dict(name="Timeout", default=opt_timeout, type="int"), + dict(name="DeleteOnExit", default=opt_delete_at_exit, type="bool"), dict(name="target_args", default=serialize(format='json', item=target_args), type="json"), diff --git a/artifacts/testdata/server/testcases/atexit.out.yaml b/artifacts/testdata/server/testcases/atexit.out.yaml index 78be35963f7..e5db321578d 100644 --- a/artifacts/testdata/server/testcases/atexit.out.yaml +++ b/artifacts/testdata/server/testcases/atexit.out.yaml @@ -12,7 +12,7 @@ SELECT * FROM query(query=''' LET Foo = SELECT log(message="I Ran at the end") F "Log": "Velociraptor: DEFAULT:Second!\n" }, { - "Log": "Velociraptor: Running AtExit query\n" + "Log": "Velociraptor: Running AtExit query SELECT log(message=\"I Ran at the end\") FROM scope()\n" }, { "Log": "Velociraptor: DEFAULT:I Ran at the end\n" diff --git a/bin/offline.go b/bin/offline.go index dbe43b50555..ae061d3f21a 100644 --- a/bin/offline.go +++ b/bin/offline.go @@ -90,6 +90,13 @@ OptTimeout: 0 # the packed builtin artifacts are only compatible with the current # release version. OptVersion: "" + +# If specified the collection will be deleted at exit. This only +# makes sense when uploading to the cloud or a remote +# location. NOTE: There is no way to check that the upload +# actually worked so this flag deletes the collection regardless +# of upload success. +OptDeleteAtExit: N ` func doCollector() error { @@ -219,7 +226,8 @@ SELECT * FROM Artifact.Server.Utils.CreateCollector( opt_cpu_limit=Spec.OptCpuLimit, opt_progress_timeout=Spec.OptProgressTimeout, opt_timeout=Spec.OptTimeout, - opt_version=Spec.OptVersion + opt_version=Spec.OptVersion, + opt_delete_at_exit=Spec.OptDeleteAtExit ) ` return runQueryWithEnv(query, builder, "json") diff --git a/docs/offline_collector/offline_collector.sh b/docs/offline_collector/offline_collector.sh index 7c09d41c45e..6978d4877b8 100755 --- a/docs/offline_collector/offline_collector.sh +++ b/docs/offline_collector/offline_collector.sh @@ -68,7 +68,8 @@ SELECT * FROM Artifact.Server.Utils.CreateCollector( opt_cpu_limit=Spec.OptCpuLimit, opt_progress_timeout=Spec.OptProgressTimeout, opt_timeout=Spec.OptTimeout, - opt_version=Spec.OptVersion + opt_version=Spec.OptVersion, + opt_delete_at_exit=Spec.OptDeleteAtExit ) EOF ) diff --git a/docs/offline_collector/sample.spec.yaml b/docs/offline_collector/sample.spec.yaml index d2130f68359..b99b7be353e 100644 --- a/docs/offline_collector/sample.spec.yaml +++ b/docs/offline_collector/sample.spec.yaml @@ -60,3 +60,10 @@ OptTimeout: 0 # the packed builtin artifacts are only compatible with the current # release version. OptVersion: "" + +# If specified the collection will be deleted at exit. This only +# makes sense when uploading to the cloud or a remote +# location. NOTE: There is no way to check that the upload +# actually worked so this flag deletes the collection regardless +# of upload success. +OptDeleteAtExit: N diff --git a/gui/velociraptor/package-lock.json b/gui/velociraptor/package-lock.json index 73c31f1096b..11dda7e6107 100644 --- a/gui/velociraptor/package-lock.json +++ b/gui/velociraptor/package-lock.json @@ -10,8 +10,8 @@ "hasInstallScript": true, "dependencies": { "@babel/runtime": "^7.26.0", - "@fortawesome/fontawesome-svg-core": "^6.7.1", - "@fortawesome/free-regular-svg-icons": "6.6.0", + "@fortawesome/fontawesome-svg-core": "6.7.1", + "@fortawesome/free-regular-svg-icons": "6.7.1", "@fortawesome/free-solid-svg-icons": "^6.7.1", "@fortawesome/react-fontawesome": "0.2.2", "@popperjs/core": "^2.11.8", @@ -22,7 +22,7 @@ "classnames": "^2.5.1", "csv-parse": "4.16.3", "csv-stringify": "5.6.5", - "dompurify": "^3.2.1", + "dompurify": "3.2.1", "env-cmd": "^10.1.0", "hosted-git-info": "^2.8.9", "html-react-parser": "^0.14.3", @@ -39,7 +39,7 @@ "react": "^16.14.0", "react-ace": "^9.5.0", "react-autosuggest": "^10.1.0", - "react-bootstrap": "^2.10.6", + "react-bootstrap": "2.10.6", "react-calendar-timeline": "^0.28.0", "react-contexify": "5.0.0", "react-dom": "^16.14.0", @@ -2663,10 +2663,9 @@ } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", - "license": "MIT", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", + "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", "engines": { "node": ">=6" } @@ -2683,20 +2682,10 @@ "node": ">=6" } }, - "node_modules/@fortawesome/fontawesome-svg-core/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@fortawesome/free-regular-svg-icons": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.1.tgz", "integrity": "sha512-e13cp+bAx716RZOTQ59DhqikAgETA9u1qTBHO3e3jMQQ+4H/N1NC1ZVeFYt1V0m+Th68BrEL1/X6XplISutbXg==", - "license": "(CC-BY-4.0 AND MIT)", "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.1" }, @@ -2704,15 +2693,6 @@ "node": ">=6" } }, - "node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.1.tgz", @@ -2725,15 +2705,6 @@ "node": ">=6" } }, - "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@fortawesome/react-fontawesome": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", @@ -11209,9 +11180,9 @@ "dev": true }, "@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==" + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", + "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==" }, "@fortawesome/fontawesome-svg-core": { "version": "6.7.1", @@ -11219,13 +11190,6 @@ "integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==", "requires": { "@fortawesome/fontawesome-common-types": "6.7.1" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==" - } } }, "@fortawesome/free-regular-svg-icons": { @@ -11234,13 +11198,6 @@ "integrity": "sha512-e13cp+bAx716RZOTQ59DhqikAgETA9u1qTBHO3e3jMQQ+4H/N1NC1ZVeFYt1V0m+Th68BrEL1/X6XplISutbXg==", "requires": { "@fortawesome/fontawesome-common-types": "6.7.1" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==" - } } }, "@fortawesome/free-solid-svg-icons": { @@ -11249,13 +11206,6 @@ "integrity": "sha512-BTKc0b0mgjWZ2UDKVgmwaE0qt0cZs6ITcDgjrti5f/ki7aF5zs+N91V6hitGo3TItCFtnKg6cUVGdTmBFICFRg==", "requires": { "@fortawesome/fontawesome-common-types": "6.7.1" - }, - "dependencies": { - "@fortawesome/fontawesome-common-types": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz", - "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==" - } } }, "@fortawesome/react-fontawesome": { diff --git a/gui/velociraptor/package.json b/gui/velociraptor/package.json index fdd4c741ffa..928db30a72c 100644 --- a/gui/velociraptor/package.json +++ b/gui/velociraptor/package.json @@ -6,7 +6,7 @@ "dependencies": { "@babel/runtime": "^7.26.0", "@fortawesome/fontawesome-svg-core": "6.7.1", - "@fortawesome/free-regular-svg-icons": "6.6.0", + "@fortawesome/free-regular-svg-icons": "6.7.1", "@fortawesome/free-solid-svg-icons": "^6.7.1", "@fortawesome/react-fontawesome": "0.2.2", "@popperjs/core": "^2.11.8", diff --git a/gui/velociraptor/src/components/flows/offline-collector.jsx b/gui/velociraptor/src/components/flows/offline-collector.jsx index 48580a6e675..4629aadd7fc 100644 --- a/gui/velociraptor/src/components/flows/offline-collector.jsx +++ b/gui/velociraptor/src/components/flows/offline-collector.jsx @@ -563,13 +563,14 @@ class OfflineCollectorParameters extends React.Component { {T("Pause For Prompt")} { + let value = "N"; if (e.currentTarget.checked) { - this.props.parameters.opt_prompt = "Y"; - } else { - this.props.parameters.opt_prompt = "N"; - } + value = "Y"; + }; + this.props.parameters.opt_prompt = value; this.props.setParameters(this.props.parameters); }} checked={this.props.parameters.opt_prompt === "Y"} @@ -622,6 +623,24 @@ class OfflineCollectorParameters extends React.Component { /> + + {T("Delete Collection at Exit")} + + { + let value = "N"; + if (e.currentTarget.checked) { + value = "Y"; + } + this.props.parameters.opt_delete_at_exit = value; + this.props.setParameters(this.props.parameters); + }} + checked={this.props.parameters.opt_delete_at_exit === "Y"} + /> + + @@ -788,6 +807,7 @@ function getDefaultCollectionParameters() { opt_tempdir: undefined, opt_filename_template: "Collection-%FQDN%-%TIMESTAMP%", opt_collector_filename: undefined, + opt_delete_at_exit: "N", opt_format: "jsonl", opt_prompt: "N", }; @@ -872,6 +892,9 @@ export default class OfflineCollectorWizard extends React.Component { case "opt_prompt": collector_parameters.opt_prompt = value; break; + case "opt_delete_at_exit": + collector_parameters.opt_delete_at_exit = value; + break; case "opt_tempdir": collector_parameters.opt_tempdir = value; break; @@ -890,7 +913,6 @@ export default class OfflineCollectorWizard extends React.Component { case "opt_collector_filename": collector_parameters.opt_collector_filename = value; break; - case "opt_progress_timeout": resources.progress_timeout = JSONparse(value); break; @@ -958,6 +980,7 @@ export default class OfflineCollectorWizard extends React.Component { env.push({key: "opt_output_directory", value: str(params.opt_output_directory)}); env.push({key: "opt_filename_template", value: str(params.opt_filename_template)}); env.push({key: "opt_collector_filename", value: str(params.opt_collector_filename)}); + env.push({key: "opt_delete_at_exit", value: str(params.opt_delete_at_exit)}); env.push({key: "opt_progress_timeout", value: str(this.state.resources.progress_timeout)}); env.push({key: "opt_timeout", value: str(this.state.resources.timeout)}); env.push({key: "opt_cpu_limit", value: str( this.state.resources.cpu_limit)}); diff --git a/vql/tools/atexit.go b/vql/tools/atexit.go index 05441d4fed1..ac25f907952 100644 --- a/vql/tools/atexit.go +++ b/vql/tools/atexit.go @@ -40,20 +40,20 @@ func (self AtExitFunction) Call( switch t := arg.Query.(type) { case types.StoredQuery: + subscope := scope.Copy() + subscope.AppendVars(arg.Env) + vql_subsystem.GetRootScope(scope).AddDestructor(func() { - scope.Log("Running AtExit query") + scope.Log("Running AtExit query %v", vfilter.FormatToString(scope, t)) // We need to create a new context to run the - // desctructors in because the main context + // destructors in because the main context // may already be cancelled. ctx, cancel := context.WithTimeout( context.Background(), time.Duration(timeout)*time.Second) defer cancel() - subscope := scope.Copy() - subscope.AppendVars(arg.Env) - for _ = range t.Eval(ctx, subscope) { } }) @@ -61,7 +61,7 @@ func (self AtExitFunction) Call( scope.Log("atexit: Query type %T not supported.", arg.Query) } - return vfilter.Null{} + return true } func (self AtExitFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {