Skip to content

Commit

Permalink
A number of changes:
Browse files Browse the repository at this point in the history
* Updating PyYAML, Jinja2 and Fleetspeak dependencies.
* Artifacts work.
  • Loading branch information
mbushkov committed May 12, 2021
1 parent 6567156 commit 6aec264
Show file tree
Hide file tree
Showing 25 changed files with 816 additions and 185 deletions.
2 changes: 1 addition & 1 deletion grr/client_builder/setup.py
Expand Up @@ -71,7 +71,7 @@ def make_release_tree(self, base_dir, files):
"grr-response-client==%s" % VERSION.get("Version", "packagedepends"),
"grr-response-core==%s" % VERSION.get("Version", "packagedepends"),
"PyInstaller==3.6",
"fleetspeak-client-bin==0.1.7.4",
"fleetspeak-client-bin==0.1.9",
"olefile==0.46",
],

Expand Down
12 changes: 12 additions & 0 deletions grr/core/grr_response_core/lib/rdfvalues/artifacts.py
Expand Up @@ -413,6 +413,18 @@ def Validate(self):
raise ValueError("No artifacts to collect.")


class ArtifactProgress(rdf_structs.RDFProtoStruct):
"""Collection progress of an Artifact."""
protobuf = flows_pb2.ArtifactProgress
rdf_deps = []


class ArtifactCollectorFlowProgress(rdf_structs.RDFProtoStruct):
"""Collection progress of ArtifactCollectorFlow."""
protobuf = flows_pb2.ArtifactCollectorFlowProgress
rdf_deps = [ArtifactProgress]


class ClientArtifactCollectorArgs(rdf_structs.RDFProtoStruct):
"""An RDFValue representation of an artifact bundle."""
protobuf = artifact_pb2.ClientArtifactCollectorArgs
Expand Down
2 changes: 1 addition & 1 deletion grr/core/setup.py
Expand Up @@ -152,7 +152,7 @@ def make_release_tree(self, base_dir, files):
"python-dateutil==2.8.1",
"pytsk3==20200117",
"pytz==2020.1",
"PyYAML==5.3.1",
"PyYAML==5.4.1",
"requests==2.25.1",
"yara-python==4.0.1",
],
Expand Down
9 changes: 9 additions & 0 deletions grr/proto/grr_response_proto/flows.proto
Expand Up @@ -723,6 +723,15 @@ message ArtifactCollectorFlowArgs {
}];
}

message ArtifactProgress {
optional string name = 1;
optional uint32 num_results = 2;
}

message ArtifactCollectorFlowProgress {
repeated ArtifactProgress artifacts = 1;
}

// Next field ID: 11
message ArtifactFilesDownloaderFlowArgs {
repeated string artifact_list = 1 [(sem_type) = {
Expand Down
21 changes: 21 additions & 0 deletions grr/server/grr_response_server/flows/general/collectors.py
Expand Up @@ -88,6 +88,7 @@ class ArtifactCollectorFlow(flow_base.FlowBase):

category = "/Collectors/"
args_type = rdf_artifacts.ArtifactCollectorFlowArgs
progress_type = rdf_artifacts.ArtifactCollectorFlowProgress
behaviours = flow_base.BEHAVIOUR_BASIC

def Start(self):
Expand All @@ -98,6 +99,7 @@ def Start(self):
self.state.failed_count = 0
self.state.knowledge_base = self.args.knowledge_base
self.state.response_count = 0
self.state.progress = rdf_artifacts.ArtifactCollectorFlowProgress()

if self.args.use_tsk and self.args.use_raw_filesystem_access:
raise ValueError(
Expand Down Expand Up @@ -162,6 +164,9 @@ def Collect(self, artifact_obj):
"""Collect the raw data from the client for this artifact."""
artifact_name = artifact_obj.name

# Ensure attempted artifacts are shown in progress, even with 0 results.
self._GetOrInsertArtifactProgress(artifact_name)

test_conditions = list(artifact_obj.conditions)
os_conditions = ConvertSupportedOSToConditions(artifact_obj)
if os_conditions:
Expand Down Expand Up @@ -748,6 +753,10 @@ def _ParseResponses(self, responses, artifact_name, source):
else:
results = responses

# Increment artifact result count in flow progress.
progress = self._GetOrInsertArtifactProgress(artifact_name)
progress.num_results += len(results)

for result in results:
result_type = result.__class__.__name__
if result_type == "Anomaly":
Expand All @@ -756,6 +765,18 @@ def _ParseResponses(self, responses, artifact_name, source):
self.state.response_count += 1
self.SendReply(result, tag="artifact:%s" % artifact_name)

def GetProgress(self) -> rdf_artifacts.ArtifactCollectorFlowProgress:
return self.state.progress

def _GetOrInsertArtifactProgress(self,
name: str) -> rdf_artifacts.ArtifactProgress:
try:
return next(a for a in self.state.progress.artifacts if a.name == name)
except StopIteration:
progress = rdf_artifacts.ArtifactProgress(name=name)
self.state.progress.artifacts.append(progress)
return progress

def End(self, responses):
del responses
# If we got no responses, and user asked for it, we error out.
Expand Down
50 changes: 50 additions & 0 deletions grr/server/grr_response_server/flows/general/collectors_test.py
Expand Up @@ -452,6 +452,56 @@ def testParsingFailure(self):
results = flow_test_lib.GetFlowResults(client_id, flow_id)
self.assertEmpty(results)

def testFlowProgressHasEntryForArtifactWithoutResults(self):

client_id = self.SetupClient(0, system="Linux")
with utils.Stubber(psutil, "process_iter", lambda: iter([])):
client_mock = action_mocks.ActionMock(standard.ListProcesses)

self.fakeartifact.sources.append(
rdf_artifacts.ArtifactSource(
type=rdf_artifacts.ArtifactSource.SourceType.GRR_CLIENT_ACTION,
attributes={"client_action": standard.ListProcesses.__name__}))

flow_id = flow_test_lib.TestFlowHelper(
collectors.ArtifactCollectorFlow.__name__,
client_mock,
artifact_list=["FakeArtifact"],
client_id=client_id)

progress = flow_test_lib.GetFlowProgress(client_id, flow_id)
self.assertLen(progress.artifacts, 1)
self.assertEqual(progress.artifacts[0].name, "FakeArtifact")
self.assertEqual(progress.artifacts[0].num_results, 0)

def testFlowProgressIsCountingResults(self):

def _Iter():
return iter([
client_test_lib.MockWindowsProcess(),
client_test_lib.MockWindowsProcess()
])

client_id = self.SetupClient(0, system="Linux")
with utils.Stubber(psutil, "process_iter", _Iter):
client_mock = action_mocks.ActionMock(standard.ListProcesses)

self.fakeartifact.sources.append(
rdf_artifacts.ArtifactSource(
type=rdf_artifacts.ArtifactSource.SourceType.GRR_CLIENT_ACTION,
attributes={"client_action": standard.ListProcesses.__name__}))

flow_id = flow_test_lib.TestFlowHelper(
collectors.ArtifactCollectorFlow.__name__,
client_mock,
artifact_list=["FakeArtifact"],
client_id=client_id)

progress = flow_test_lib.GetFlowProgress(client_id, flow_id)
self.assertLen(progress.artifacts, 1)
self.assertEqual(progress.artifacts[0].name, "FakeArtifact")
self.assertEqual(progress.artifacts[0].num_results, 2)


class RelationalTestArtifactCollectors(ArtifactCollectorsTestMixin,
test_lib.GRRBaseTest):
Expand Down
Expand Up @@ -25,7 +25,8 @@
</div>
</div>

<div class="client-approval mat-elevation-z5">
<div class="client-approval mat-elevation-z5"
[style.display]="(this.showApprovalView$ | async) ? 'block' : 'none'">
<approval></approval>
</div>
</mat-drawer-content>
Expand Down
Expand Up @@ -53,12 +53,15 @@ export class ClientPage implements OnInit, AfterViewInit, OnDestroy {

@ViewChild('clientDetailsDrawer') clientDetailsDrawer!: MatDrawer;

@ViewChild(Approval, {read: ElementRef}) approvalViewContainer!: ElementRef;
@ViewChild(Approval, {read: ElementRef}) approvalViewContainer?: ElementRef;

approvalHeight: number = 0;

readonly showApprovalView$ = this.clientPageFacade.approvalsEnabled$;

private readonly resizeObserver = new ResizeObserver(() => {
this.approvalHeight = this.approvalViewContainer.nativeElement.offsetHeight;
this.approvalHeight =
this.approvalViewContainer?.nativeElement.offsetHeight ?? 0;
this.changeDetectorRef.markForCheck();
});

Expand Down Expand Up @@ -88,7 +91,9 @@ export class ClientPage implements OnInit, AfterViewInit, OnDestroy {
}

ngAfterViewInit() {
this.resizeObserver.observe(this.approvalViewContainer.nativeElement);
if (this.approvalViewContainer !== undefined) {
this.resizeObserver.observe(this.approvalViewContainer.nativeElement);
}

const urlTokens = this.router.routerState.snapshot.url.split('/');
if (urlTokens[urlTokens.length - 1] === ClientPage.CLIENT_DETAILS_ROUTE) {
Expand Down
Expand Up @@ -161,4 +161,28 @@ describe('ClientPage Component', () => {
tick();
discardPeriodicTasks();
}));

it('shows approval iff approvalsEnabled$', () => {
const fixture = TestBed.createComponent(ClientComponent);
fixture.detectChanges();

clientPageFacade.approvalsEnabledSubject.next(true);
fixture.detectChanges();

expect(fixture.debugElement.query(By.css('.client-approval'))
.styles['display'])
.toEqual('block');
});

it('does not show approval if approvalsEnabled$ is false', () => {
const fixture = TestBed.createComponent(ClientComponent);
fixture.detectChanges();

clientPageFacade.approvalsEnabledSubject.next(false);
fixture.detectChanges();

expect(fixture.debugElement.query(By.css('.client-approval'))
.styles['display'])
.toEqual('none');
});
});
Expand Up @@ -13,6 +13,7 @@ import {TimestampModule} from '@app/components/timestamp/module';

import {FileResultsTable} from './file_results_table';
import {OsqueryResultsTable} from './osquery_results_table';
import {ResultAccordion} from './result_accordion';


/**
Expand All @@ -37,11 +38,13 @@ import {OsqueryResultsTable} from './osquery_results_table';
FileResultsTable,
FileModePipe,
OsqueryResultsTable,
ResultAccordion,
],
exports: [
FileResultsTable,
FileModePipe,
OsqueryResultsTable,
ResultAccordion,
],
})
export class HelpersModule {
Expand Down
@@ -0,0 +1,23 @@
<div class="row-list">
<div class="row">
<div class="header" (click)="toggle()">
<div class="title">
<span>{{ title }}</span>
</div>

<div class="expansion-indicator">
<span *ngIf="isOpen || hasMoreResults || hasResults" class="material-icons arrow-icon">
{{ isOpen ? 'keyboard_arrow_down' : 'keyboard_arrow_right' }}
</span>
</div>
</div>

<div class="results" *ngIf="isOpen">
<ng-content></ng-content>

<button class="expand-results" mat-button aria-label="Load more results" (click)="loadMoreClicked()" *ngIf="hasMoreResults">
<mat-icon>expand_more</mat-icon>
</button>
</div>
</div>
</div>
@@ -0,0 +1,100 @@
@use '~material-theme' as c;

$header-left-padding: 24px;
$files-counter-offset: 135px;

.row-list {
margin-bottom: 0.15em;

.row {
.header {
cursor: pointer;

position: relative;
bottom: -2px;
&:hover {
background-color: c.mat-color(c.$primary, 100, 0.15);
}

border-top: 1px solid c.mat-color(c.$foreground, divider-light);
padding: 0.5em 0 0.5em $header-left-padding;

display: flex;
align-items: center;

.row-text {
position: relative;
right: $files-counter-offset;
white-space: nowrap;
}

.title,
.in-progress,
.success,
.warning,
.error {
display: flex;
align-items: center;

.material-icons {
position: relative;
top: -1px;
}
}

.title {
flex-grow: 1;
font-family: c.$google-sans-display-family;
font-weight: 400;

.arrow-icon {
font-size: 36px;
}
}

.success {
color: c.mat-color(c.$foreground, text-light);
font-size: 90%;

.material-icons {
color: c.mat-color(c.$foreground, success);
margin-left: 0.5em;
}
}

.warning {
color: c.mat-color(c.$warn, darker);
font-size: 90%;

.material-icons {
color: c.mat-color(c.$warn);
margin-left: 0.5em;
}
}

.error {
color: c.mat-color(c.$danger);
font-size: 90%;

.material-icons {
margin-left: 0.5em;
}
}

.expansion-indicator {
color: c.mat-color(c.$foreground, divider);
font-size: 30px;
text-align: center;
width: 40px;
}
}

.results-in-progress {
padding: 0.5em 24px;
}
}
}

.expand-results {
width: 100%;
}

0 comments on commit 6aec264

Please sign in to comment.