Skip to content

Commit

Permalink
Manage sketch attributes in the CLI client (#2841)
Browse files Browse the repository at this point in the history
* add json as output to the sketch describe command and add sketch attributes to the text output

* deprecated sketch.attributes_table per #2839

* add attributes to the sketch cli command

* react on the return code of the API in case deletion is ot possible

* add documentation

* feedback

* lint

* modify so if a output format is passed that is not supported to get an error

* update documentation

* linter

* move sketch attributes to a dedicated command group nested in sketch attribute group

* rename d to sketches

* Update cli_client/python/timesketch_cli_client/commands/attribute.py

Co-authored-by: Johan Berggren <[email protected]>

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update docs/guides/user/cli-client.md

* Update cli_client/python/timesketch_cli_client/commands/attribute.py

---------

Co-authored-by: Johan Berggren <[email protected]>
  • Loading branch information
jaegeral and berggren authored Jul 21, 2023
1 parent df3c0db commit 0c02445
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 10 deletions.
6 changes: 5 additions & 1 deletion api_client/python/timesketch_api_client/sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ def attributes(self):

@property
def attributes_table(self):
"""Property that returns the sketch attributes as a data frame."""
"""DEPRECATED: Property that returns the sketch attributes
as a data frame.
Given the fluid setup of attributes, this is not a good way to
represent the data. Use the attributes property instead."""
data = self.lazyload_data(refresh_cache=True)
meta = data.get("meta", {})
attributes = meta.get("attributes", [])
Expand Down
95 changes: 95 additions & 0 deletions cli_client/python/timesketch_cli_client/commands/attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright 2023 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Commands for sketch attributes."""

import json
import click


@click.group("attributes", help="Manage attributes of a sketch.")
def attribute_group():
"""Manage attributes of a sketch.
This group is nested in the sketch group."""


@attribute_group.command("list", help="List all attributes.")
@click.pass_context
def list_attributes(ctx):
"""List all attributes."""
sketch = ctx.obj.sketch
output = ctx.obj.output_format
attributes = sketch.attributes
if not attributes:
click.echo("No attributes found.")
ctx.exit(1)
if output == "json":
click.echo(json.dumps(attributes, indent=4, sort_keys=True, default=str))
elif output == "text":
for k, v in attributes.items():
click.echo(f"Name: {k}: Ontology: {v['ontology']} Value: {v['value']}")
else: # format not implemented use json or text instead
click.echo(f"Output format {output} not implemented.")


@attribute_group.command("remove", help="Remove an attribute from a Sketch.")
@click.option("--name", required=True, help="Name of the attribute.")
@click.option("--ontology", required=True, help="Ontology of the attribute.")
@click.pass_context
def remove_attribute(ctx, name, ontology):
"""Remove an attribute from a sketch.
Args:
name: Name of the attribute.
ontology: Ontology of the attribute.
"""
sketch = ctx.obj.sketch
if ctx.obj.output_format != "text":
click.echo(
f"Output format {ctx.obj.output_format} not implemented. Use text instead."
)
ctx.exit(1)
if sketch.remove_attribute(name, ontology):
click.echo(f"Attribute removed: Name: {name} Ontology: {ontology}")
else:
click.echo(f"Attribute not found: Name: {name} Ontology: {ontology}")
ctx.exit(1)


@attribute_group.command("add", help="Add an attribute to a Sketch.")
@click.option("--name", required=True, help="Name of the attribute.")
@click.option("--ontology", required=True, help="Ontology of the attribute.")
@click.option("--value", required=True, help="Value of the attribute.")
@click.pass_context
def add_attribute(ctx, name, ontology, value):
"""Add an attribute to a sketch.
Args:
name: Name of the attribute.
ontology: Ontology of the attribute.
value: Value of the attribute.
Example:
timesketch sketch add_attribute
--name ticket_id --ontology text --value 12345
"""
sketch = ctx.obj.sketch
if ctx.obj.output_format != "text":
click.echo(f"Output format {ctx.obj.output_format} not implemented.")
ctx.exit(1)
sketch.add_attribute(name, ontology, value)
click.echo("Attribute added:")
click.echo(f"Name: {name}")
click.echo(f"Ontology: {ontology}")
click.echo(f"Value: {value}")
56 changes: 47 additions & 9 deletions cli_client/python/timesketch_cli_client/commands/sketch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,67 @@
# limitations under the License.
"""Commands for sketches."""

import json
import click
import pandas as pd

from timesketch_cli_client.commands import attribute as attribute_command


@click.group("sketch")
def sketch_group():
"""Manage sketch."""


@sketch_group.command("list")
# Add the attribute command group to the sketch command group.
sketch_group.add_command(attribute_command.attribute_group)


@sketch_group.command("list", help="List all sketches.")
@click.pass_context
def list_sketches(ctx):
"""List all sketches."""
api_client = ctx.obj.api
output = ctx.obj.output_format
sketches = []

for sketch in api_client.list_sketches():
click.echo(f"{sketch.id} {sketch.name}")
sketches.append({"id": sketch.id, "name": sketch.name})

sketch_panda = pd.DataFrame(sketches, columns=["id", "name"])
if output == "json":
click.echo(sketch_panda.to_json(orient="records", indent=4))
elif output == "text":
click.echo(f"{sketch_panda.to_string(index=False)}")
else:
click.echo(f"Output format {output} not implemented.")
ctx.exit(1)

@sketch_group.command("describe")

@sketch_group.command(
"describe",
help="Describe the active sketch",
)
@click.pass_context
def describe_sketch(ctx):
"""Show info about the active sketch."""
"""Describe the active sketch.
Attributes only in JSON output format."""
sketch = ctx.obj.sketch
# TODO (berggren): Add more details to the output.
click.echo(f"Name: {sketch.name}")
click.echo(f"Description: {sketch.description}")
output = ctx.obj.output_format

if output == "json":
click.echo(json.dumps(sketch.__dict__, indent=4, sort_keys=True, default=str))
return
if output == "text":
click.echo(f"Name: {sketch.name}")
click.echo(f"Description: {sketch.description}")
click.echo(f"Status: {sketch.status}")
else:
click.echo(f"Output format {output} not implemented.")
ctx.exit(1)

@sketch_group.command("create")

@sketch_group.command("create", help="Create a new sketch [text].")
@click.option("--name", required=True, help="Name of the sketch.")
@click.option(
"--description",
Expand All @@ -49,7 +82,12 @@ def describe_sketch(ctx):
)
@click.pass_context
def create_sketch(ctx, name, description):
"""Create a new sketch."""
"""Create a new sketch.
Args:
name: Name of the sketch.
description: Description of the sketch (optional).
"""
api_client = ctx.obj.api
if not description:
description = name
Expand Down
5 changes: 5 additions & 0 deletions docs/developers/cli-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ care of setting up authentication, sending the API calls to the server, error ha

This documentation will give an overview for the most common developer use cases of the CLI client.

The main target audience for the CLI client is Timesketch user / analyst.
While some methods might have verbose JSON output, the default should be as tailored to analyst needs as the WebUI.

## Basic Connections

The CLI client defines a config library specifically intended to help with setting up all configuration for connecting to Timesketch, including
Expand Down Expand Up @@ -101,6 +104,8 @@ And when data is about to be put out:
click.echo(sigma_rules.to_string(index=header, columns=columns))
```

If a specific output format is not implemented, it is best practice to error out and tell the user which formats are implemented.

Depending on the command, some output formats might not make sense.

As shown in the code, there is also an option for `header` and `columns`
Expand Down
151 changes: 151 additions & 0 deletions docs/guides/user/cli-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,157 @@ This example returns the field name `domain` and then do a simple sort and uniq.
timesketch search -q "foobar" --return-fields domain | sort | uniq
```

## Sketch

### List all sketches

To list all sketches you have access to:

```bash
timesketch --output-format text sketch list
id name
2 asdasd
1 aaaa
```

You can also get a list as JSON if you like to:

```
timesketch --output-format json sketch list
[
{
"id":2,
"name":"asdasd"
},
{
"id":1,
"name":"aaaa"
}
]
```

### Get description for one sketch

Getting information about a sketch can be helpful in various situations.

```bash
timesketch --output-format text sketch describe
Name: asdasd
Description: None
Status: new
```

You can also get all stored information about a sketch with running:
```bash
timesketch --output-format json sketch describe
```

This will give you something like:

```json
timesketch --output-format json sketch describe
{
"_archived": null,
"_sketch_name": "asdasd",
"api": "<timesketch_api_client.client.TimesketchApi object at 0x7f3375d466e0>",
"id": 2,
"resource_data": {
"meta": {
"aggregators": {
...
```

### Get attributes

Attributes can be to long to show in `sketch describe` which is why there is a
separate command for it:

```timesketch sketch attributes```

Will give back something like this:

```bash
timesketch --output-format text sketch attributes
Name: intelligence: Ontology: intelligence Value: {'data': [{'externalURI': 'google.com', 'ioc': '1.2.3.4', 'tags': ['foo'], 'type': 'ipv4'}, {'externalURI': 'fobar.com', 'ioc': '3.3.3.3', 'tags': ['aaaa'], 'type': 'ipv4'}]}
Name: ticket_id: Ontology: 12345 Value: text
Name: ticket_id2: Ontology: 12345 Value: text
Name: ticket_id3: Ontology: 12345 Value: text
```

Or as JSON

```
timesketch --output-format json sketch attributes
{
"intelligence": {
"ontology": "intelligence",
"value": {
"data": [
{
"externalURI": "google.com",
"ioc": "1.2.3.4",
"tags": [
"foo"
],
"type": "ipv4"
},
{
"externalURI": "fobar.com",
"ioc": "3.3.3.3",
"tags": [
"aaaa"
],
"type": "ipv4"
}
]
}
},
"ticket_id": {
"ontology": "12345",
"value": "text"
},
"ticket_id2": {
"ontology": "12345",
"value": "text"
},
"ticket_id3": {
"ontology": "12345",
"value": "text"
}
}
```

### Add a attribute

To add an attribute to a sketch

```bash
timesketch sketch add_attribute
```

For example:

```bash
timesketch sketch add_attribute --name ticket_id3 --ontology text --value 12345
Attribute added:
Name: ticket_id3
Ontology: text
Value: 12345
```

To verify, run `timesketch sketch attributes`.

### Remove an attribute

To remove an attribute from a sketch

```bash
timesketch sketch remove_attribute
```


## Run analyzers

## Analyzers

### List
Expand Down

0 comments on commit 0c02445

Please sign in to comment.