Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,76 @@ $ faraday-cli host list
575 127.0.0.9 unknown 1 3
590 58.76.184.4 unknown www.googlec.com 0 -
```
### Update Hosts

```shell script
$ faraday-cli host update 12 -d '[{"ip":"127.0.0.1", "hostnames":["test.test.es"]}]'
Updated host:
{
"services": 11,
"owner": "faraday",
"description": "",
"ip": "127.0.0.1",
"_rev": "",
"workspace_name": "TEST",
"hostnames": [
"127.0.0.1",
"feeds.cti.pers.eus"
],
"name": "127.0.0.1",
"vulns": 14,
"metadata": {
"update_user": null,
"owner": "faraday",
"update_controller_action": "",
"update_action": 0,
"command_id": null,
"creator": "",
"update_time": "2025-04-03T11:42:02.417316+00:00",
"create_time": "2025-03-31T14:13:22.684505+00:00"
},
"_id": 12,
"mac": "00:00:00:00:00:00",
"type": "Host",
"versions": [
"OpenSSH 8.9p1 Ubuntu 3ubuntu0.11",
"nginx 1.18.0",
"",
"Nagios NSCA",
"",
"Golang net/http server",
"MinIO S3-compatible object store",
"Elasticsearch REST API 8.17.3",
"Elasticsearch binary API",
"Golang net/http server"
],
"id": 12,
"service_summaries": [
"(22/tcp) ssh (OpenSSH 8.9p1 Ubuntu 3ubuntu0.11)",
"(80/tcp) http (nginx 1.18.0)",
"(2377/tcp) grpc",
"(7946/tcp) unknown",
"(8000/tcp) nagios-nsca (Nagios NSCA)",
"(8080/tcp) http-proxy",
"(9000/tcp) http (Golang net/http server)",
"(9001/tcp) http (MinIO S3-compatible object store)",
"(9200/tcp) http (Elasticsearch REST API 8.17.3)",
"(9300/tcp) elasticsearch (Elasticsearch binary API)",
"(9443/tcp) https (Golang net/http server)"
],
"owned": false,
"default_gateway": "",
"os": "Linux",
"credentials": 0,
"importance": 0
}
```
### Create Vuln

```shell script
$ faraday-cli vuln create -d '{"name": "process injection", "description": "inyeccion de procesos", "exploitation": "medium", "type": ["vulnerability_template"]}'
Vulnerability created with ID: 89
```

### Get host

Expand Down
5 changes: 5 additions & 0 deletions faraday_cli/api_client/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ class RequestError(ErrorWithResponse):

class ExpiredLicense(Exception):
pass

class HostNotFoundError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
36 changes: 36 additions & 0 deletions faraday_cli/api_client/faraday_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
InvalidCredentials,
Invalid2FA,
MissingConfig,
HostNotFoundError,
ExpiredLicense,
NotFound,
RequestError,
Expand Down Expand Up @@ -68,6 +69,8 @@ def hanlde(self, *args, **kwargs):
raise Exception(f"{e}")
except NotFoundError:
raise NotFound("Element not found")
except HostNotFoundError as e:
raise Exception(f"Host not found: {e}")
except ClientError as e:
if e.response.status_code == 402:
raise ExpiredLicense("Your Faraday license is expired")
Expand Down Expand Up @@ -286,6 +289,17 @@ def get_vulns(self, workspace_name: str, query_filter: dict = None):
workspace_name, params={"q": json.dumps(query_filter)}
)
return response.body
@handle_errors
def create_vuln(self, workspace_name: str, vuln_params):
try:
response = self.faraday_api.vuln.create(workspace_name, body=vuln_params)
except ClientError as e:
if e.response.status_code == 409:
raise exceptions.DuplicatedError("Vulnerability already exists")
else:
raise
else:
return response.body

@handle_errors
def get_workspace_credentials(self, workspace_name: str):
Expand Down Expand Up @@ -354,6 +368,28 @@ def create_host(self, workspace_name: str, host_params):
raise
else:
return response.body

@handle_errors
def update_host(self, workspace_name: str, host_id, host_params):
try:
response = self.faraday_api.host.update(workspace_name, host_id, body=host_params)
current_host = response.body
if "hostnames" in host_params:
current_host["hostnames"] = host_params["hostnames"]
if "description" in host_params:
current_host["description"] = host_params["description"]

try:
update_response = self.faraday_api.host.update(workspace_name, host_id, body=current_host)
except ClientError as e:
raise Exception(f"Error while updating host: {e}")
else:
return update_response.body
except ClientError as e:
if e.response.status_code == 404:
raise exceptions.HostNotFoundError("Host not found")
else:
raise Exception(f"Error while checking host existence: {e}")

@handle_errors
def get_host_services(self, workspace_name: str, host_id):
Expand Down
3 changes: 3 additions & 0 deletions faraday_cli/api_client/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ class HostResource(Resource):
}, # workaround for api bug
"get": {"method": "GET", "url": "v3/ws/{}/hosts/{}"},
"create": {"method": "POST", "url": "v3/ws/{}/hosts"},
"update": {"method": "PATCH", "url": "v3/ws/{}/hosts/{}"},
"delete": {"method": "DELETE", "url": "v3/ws/{}/hosts/{}"},
"get_services": {
"method": "GET",
"url": "v3/ws/{}/hosts/{}/services",
},
"get_vulns": {"method": "GET", "url": "v3/ws/{}/vulns"},

}


Expand All @@ -60,6 +62,7 @@ class ServiceResource(Resource):
class VulnResource(Resource):
actions = {
"get": {"method": "GET", "url": "v3/ws/{}/vulns/{}"},
"create": {"method": "POST", "url": "v3/vulnerability_template"},
"patch": {"method": "PATCH", "url": "v3/ws/{}/vulns/{}"},
"list": {"method": "GET", "url": "v3/ws/{}/vulns"},
"filter": {"method": "GET", "url": "v3/ws/{}/vulns/filter"},
Expand Down
94 changes: 94 additions & 0 deletions faraday_cli/shell/modules/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@
},
}

HOST_UPDATE_JSON_SCHEMA = {
"type": "array",
"items": {
"type": "object",
"properties": {
"ip": {"type": "string"},
"description": {"type": "string"},
"hostnames": {
"type": "array",
"items": {"type": "string"}
},
},
"required": [],
"additionalProperties": False
},
}



class HostCommands(cmd2.CommandSet):
def __init__(self):
Expand Down Expand Up @@ -386,3 +404,79 @@ def create_hosts(self, args: argparse.Namespace):
self._cmd.poutput(
f"Created host\n{json.dumps(host, indent=4)}"
)
# Update hosts
update_host_parser = argparse.ArgumentParser()
update_host_parser.add_argument(
"-d",
"--host-data",
type=str,
help=f"Host data in json format: {HOST_UPDATE_JSON_SCHEMA}",
)
update_host_parser.add_argument(
"--stdin", action="store_true", help="Read host-data from stdin"
)
update_host_parser.add_argument("host_id", type=int, help="ID of the host")
update_host_parser.add_argument(
"-w",
"--workspace-name",
type=str,
help="Workspace name",
required=False,
)
update_host_parser.add_argument(
"--resolve-hostname",
action="store_true",
help="Doesn't resolve hostname",
)

@cmd2.as_subcommand_to(
"host", "update", update_host_parser, help="update hosts"
)
def update_hosts(self, args: argparse.Namespace):
"""Update Hosts"""
if not args.workspace_name:
if active_config.workspace:
workspace_name = active_config.workspace
else:
self._cmd.perror("No active Workspace")
return
else:
workspace_name = args.workspace_name
if args.stdin:
host_data = sys.stdin.read()
else:
if not args.host_data:
self._cmd.perror("Missing host data")
return
else:
host_data = args.host_data

try:
json_data = utils.json_schema_validator(HOST_UPDATE_JSON_SCHEMA)(host_data)
except Exception as e:
self._cmd.perror(f"JSON validation error: {e}")
return
for _host_data in json_data:
if args.resolve_hostname:
ip, hostname = utils.get_ip_and_hostname(_host_data.get("ip", ""))
else:
ip = _host_data.get("ip", "")
hostname = ip
_host_data["ip"] = ip
if hostname:
if "hostnames" in _host_data:
_host_data["hostnames"].append(hostname)
else:
_host_data["hostnames"] = [hostname]
try:
host = self._cmd.api_client.get_host(workspace_name, args.host_id)
if not host:
self._cmd.perror(f"Host with ID {args.host_id} not found")
return
updated_host = self._cmd.api_client.update_host(workspace_name, args.host_id, _host_data)

except Exception as e:
self._cmd.perror(f"Error updating host: {e}")
else:
self._cmd.poutput(f"Updated host:\n{json.dumps(updated_host, indent=4)}")

Loading