praetorian-cli 2.2.1__py3-none-any.whl → 2.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- praetorian_cli/handlers/add.py +25 -7
- praetorian_cli/handlers/aegis.py +107 -0
- praetorian_cli/handlers/delete.py +3 -2
- praetorian_cli/handlers/get.py +48 -2
- praetorian_cli/handlers/list.py +41 -9
- praetorian_cli/handlers/ssh_utils.py +154 -0
- praetorian_cli/handlers/test.py +7 -2
- praetorian_cli/handlers/update.py +3 -3
- praetorian_cli/main.py +1 -0
- praetorian_cli/sdk/chariot.py +71 -12
- praetorian_cli/sdk/entities/aegis.py +437 -0
- praetorian_cli/sdk/entities/assets.py +30 -12
- praetorian_cli/sdk/entities/scanners.py +13 -0
- praetorian_cli/sdk/entities/schema.py +27 -0
- praetorian_cli/sdk/entities/seeds.py +108 -56
- praetorian_cli/sdk/mcp_server.py +2 -3
- praetorian_cli/sdk/model/aegis.py +156 -0
- praetorian_cli/sdk/model/query.py +1 -1
- praetorian_cli/sdk/model/utils.py +2 -8
- praetorian_cli/sdk/test/pytest.ini +1 -0
- praetorian_cli/sdk/test/test_asset.py +2 -2
- praetorian_cli/sdk/test/test_seed.py +13 -14
- praetorian_cli/sdk/test/test_z_cli.py +22 -24
- praetorian_cli/sdk/test/ui_mocks.py +133 -0
- praetorian_cli/sdk/test/utils.py +16 -4
- praetorian_cli/ui/__init__.py +3 -0
- praetorian_cli/ui/aegis/__init__.py +5 -0
- praetorian_cli/ui/aegis/commands/__init__.py +2 -0
- praetorian_cli/ui/aegis/commands/help.py +81 -0
- praetorian_cli/ui/aegis/commands/info.py +136 -0
- praetorian_cli/ui/aegis/commands/job.py +381 -0
- praetorian_cli/ui/aegis/commands/list.py +14 -0
- praetorian_cli/ui/aegis/commands/set.py +32 -0
- praetorian_cli/ui/aegis/commands/ssh.py +87 -0
- praetorian_cli/ui/aegis/constants.py +20 -0
- praetorian_cli/ui/aegis/menu.py +395 -0
- praetorian_cli/ui/aegis/utils.py +162 -0
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/METADATA +4 -1
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/RECORD +43 -24
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/WHEEL +0 -0
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/entry_points.txt +0 -0
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/licenses/LICENSE +0 -0
- {praetorian_cli-2.2.1.dist-info → praetorian_cli-2.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from praetorian_cli.handlers.utils import error
|
|
2
2
|
from praetorian_cli.sdk.model.globals import Seed, Kind
|
|
3
|
+
from praetorian_cli.sdk.model.query import Query, Node, Filter, KIND_TO_LABEL
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class Seeds:
|
|
@@ -9,97 +10,148 @@ class Seeds:
|
|
|
9
10
|
def __init__(self, api):
|
|
10
11
|
self.api = api
|
|
11
12
|
|
|
12
|
-
def add(self,
|
|
13
|
+
def add(self, status=Seed.PENDING.value, seed_type=Kind.ASSET.value, **kwargs):
|
|
13
14
|
"""
|
|
14
|
-
Add a seed
|
|
15
|
-
|
|
16
|
-
:param
|
|
17
|
-
:type
|
|
18
|
-
:param
|
|
19
|
-
:type
|
|
15
|
+
Add a seed of specified type with dynamic fields.
|
|
16
|
+
|
|
17
|
+
:param status: Status for backward compatibility
|
|
18
|
+
:type status: str or None
|
|
19
|
+
:param type: Asset type (e.g., 'asset', 'addomain', etc.)
|
|
20
|
+
:type type: str
|
|
21
|
+
:param kwargs: Dynamic fields for the asset type
|
|
20
22
|
:return: The seed that was added
|
|
21
23
|
:rtype: dict
|
|
22
24
|
"""
|
|
23
|
-
|
|
25
|
+
# Handle status if provided
|
|
26
|
+
kwargs['status'] = status
|
|
27
|
+
|
|
28
|
+
# Build payload with type wrapper
|
|
29
|
+
payload = {
|
|
30
|
+
'type': seed_type,
|
|
31
|
+
'model': kwargs
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return self.api.upsert('seed', payload)
|
|
24
35
|
|
|
25
36
|
def get(self, key):
|
|
26
37
|
"""
|
|
27
38
|
Get details of a seed by key.
|
|
28
39
|
|
|
29
|
-
:param key: Entity key
|
|
40
|
+
:param key: Entity key (e.g., '#asset#example.com#example.com')
|
|
30
41
|
:type key: str
|
|
31
42
|
:return: The seed matching the specified key, or None if not found
|
|
32
43
|
:rtype: dict or None
|
|
33
44
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
|
|
46
|
+
# Create a Filter for the key field
|
|
47
|
+
key_filter = Filter(
|
|
48
|
+
field=Filter.Field.KEY,
|
|
49
|
+
operator=Filter.Operator.EQUAL,
|
|
50
|
+
value=key
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Create a Node with Seed label and key filter
|
|
54
|
+
node = Node(
|
|
55
|
+
labels=[Node.Label.SEED],
|
|
56
|
+
filters=[key_filter]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Create the Query object
|
|
60
|
+
query = Query(node=node)
|
|
61
|
+
|
|
62
|
+
# Call by_query with the constructed Query object
|
|
63
|
+
results_tuple = self.api.search.by_query(query)
|
|
64
|
+
if not results_tuple:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
results, _ = results_tuple
|
|
68
|
+
if len(results) == 0:
|
|
69
|
+
return None
|
|
70
|
+
return results[0]
|
|
71
|
+
|
|
72
|
+
def update(self, key, status=None):
|
|
37
73
|
"""
|
|
38
|
-
Update
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
internally uses the DNS of the original seed rather than the key for the update operation.
|
|
42
|
-
|
|
43
|
-
:param key: Entity key in format #seed#{type}#{dns} where type is 'domain', 'ip', or 'cidr' and dns is the seed value
|
|
74
|
+
Update seed fields dynamically.
|
|
75
|
+
|
|
76
|
+
:param key: Seed/Asset key (e.g., '#seed#domain#example.com' or '#asset#domain#example.com')
|
|
44
77
|
:type key: str
|
|
45
|
-
:param status:
|
|
46
|
-
:type status: str
|
|
78
|
+
:param status: Status for backward compatibility (can be positional)
|
|
79
|
+
:type status: str or None
|
|
80
|
+
:param kwargs: Fields to update
|
|
47
81
|
:return: The updated seed, or None if the seed was not found
|
|
48
82
|
:rtype: dict or None
|
|
49
83
|
"""
|
|
50
|
-
|
|
84
|
+
|
|
85
|
+
seed = self.get(key) # This already handles old key format conversion
|
|
51
86
|
if seed:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
update_payload = {
|
|
88
|
+
'key': key,
|
|
89
|
+
'status': status
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return self.api.upsert('seed', update_payload)
|
|
57
93
|
else:
|
|
58
|
-
error(f'Seed {key}
|
|
94
|
+
error(f'Seed {key} not found.')
|
|
59
95
|
|
|
60
96
|
def delete(self, key):
|
|
61
97
|
"""
|
|
62
|
-
Delete
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
it sets the seed's status to DELETED ('D'), which marks it as deleted while
|
|
66
|
-
preserving the record for audit purposes.
|
|
67
|
-
|
|
68
|
-
:param key: Entity key in format #seed#{type}#{dns} where type is 'domain', 'ip', or 'cidr' and dns is the seed value
|
|
98
|
+
Delete seed (supports both old and new key formats).
|
|
99
|
+
|
|
100
|
+
:param key: Seed/Asset key (e.g., '#asset#domain#example.com')
|
|
69
101
|
:type key: str
|
|
70
102
|
:return: The seed that was marked as deleted, or None if the seed was not found
|
|
71
103
|
:rtype: dict or None
|
|
72
104
|
"""
|
|
73
|
-
seed = self.
|
|
105
|
+
seed = self.get(key) # This already handles old key format conversion
|
|
106
|
+
|
|
74
107
|
if seed:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
delete_payload = {
|
|
109
|
+
'key': key,
|
|
110
|
+
'status': Seed.DELETED.value
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return self.api.upsert('seed', delete_payload)
|
|
78
114
|
else:
|
|
79
|
-
error(f'Seed {key}
|
|
115
|
+
error(f'Seed {key} not found.')
|
|
80
116
|
|
|
81
|
-
def list(self,
|
|
117
|
+
def list(self, seed_type=Kind.SEED.value, key_prefix='', pages=100000) -> tuple:
|
|
82
118
|
"""
|
|
83
|
-
List seeds with
|
|
84
|
-
|
|
85
|
-
:param
|
|
86
|
-
:
|
|
87
|
-
:param
|
|
88
|
-
:
|
|
89
|
-
:param offset: The offset of the page you want to retrieve results. If this is not supplied, this function retrieves from the first page
|
|
90
|
-
:type offset: str or None
|
|
119
|
+
List seeds by querying assets with 'Seed' label.
|
|
120
|
+
|
|
121
|
+
:param seed_type: Optional asset seed_type filter (e.g., 'asset', 'addomain')
|
|
122
|
+
:seed_type seed_type: str or None
|
|
123
|
+
:param key_prefix: Filter by key prefix
|
|
124
|
+
:seed_type key_prefix: str
|
|
91
125
|
:param pages: The number of pages of results to retrieve. <mcp>Start with one page of results unless specifically requested.</mcp>
|
|
92
|
-
:
|
|
126
|
+
:seed_type pages: int
|
|
93
127
|
:return: A tuple containing (list of seeds, next page offset)
|
|
94
|
-
:
|
|
128
|
+
:rseed_type: tuple
|
|
95
129
|
"""
|
|
96
|
-
prefix_term = '#seed#'
|
|
97
|
-
if type:
|
|
98
|
-
prefix_term = f'{prefix_term}{type}#'
|
|
99
|
-
if prefix_filter:
|
|
100
|
-
prefix_term = f'{prefix_term}{prefix_filter}'
|
|
101
130
|
|
|
102
|
-
|
|
131
|
+
if seed_type in KIND_TO_LABEL:
|
|
132
|
+
seed_type = KIND_TO_LABEL[seed_type]
|
|
133
|
+
elif not seed_type:
|
|
134
|
+
seed_type = Node.Label.SEED
|
|
135
|
+
else:
|
|
136
|
+
raise ValueError(f'Invalid seed type: {seed_type}')
|
|
137
|
+
|
|
138
|
+
node = Node(
|
|
139
|
+
labels=[seed_type],
|
|
140
|
+
filters=[]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
key_filter = Filter(
|
|
144
|
+
field=Filter.Field.KEY,
|
|
145
|
+
operator=Filter.Operator.STARTS_WITH,
|
|
146
|
+
value=key_prefix
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
if key_prefix:
|
|
150
|
+
node.filters.append(key_filter)
|
|
151
|
+
|
|
152
|
+
query = Query(node=node)
|
|
153
|
+
|
|
154
|
+
return self.api.search.by_query(query, pages)
|
|
103
155
|
|
|
104
156
|
def attributes(self, key):
|
|
105
157
|
"""
|
praetorian_cli/sdk/mcp_server.py
CHANGED
|
@@ -8,7 +8,6 @@ from mcp.server.lowlevel import Server
|
|
|
8
8
|
from mcp.server.stdio import stdio_server
|
|
9
9
|
from mcp.types import Tool, TextContent
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
class MCPServer:
|
|
13
12
|
def __init__(self, chariot_instance, allowable_tools: Optional[List[str]] = None):
|
|
14
13
|
self.chariot = chariot_instance
|
|
@@ -157,7 +156,7 @@ class MCPServer:
|
|
|
157
156
|
tools = []
|
|
158
157
|
for tool_name, tool_info in self.discovered_tools.items():
|
|
159
158
|
parameters = self._extract_parameters_from_doc(tool_info['doc'], tool_info['signature'])
|
|
160
|
-
|
|
159
|
+
|
|
161
160
|
properties = {}
|
|
162
161
|
required = []
|
|
163
162
|
|
|
@@ -170,7 +169,7 @@ class MCPServer:
|
|
|
170
169
|
"description": param_info["description"]
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
if param_info
|
|
172
|
+
if param_info.get("required", False):
|
|
174
173
|
required.append(param_name)
|
|
175
174
|
|
|
176
175
|
tool_schema = {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis agent data models and structures.
|
|
3
|
+
|
|
4
|
+
This module contains dataclass definitions for Aegis agent entities,
|
|
5
|
+
including network interfaces, tunnel status, health checks, and agent metadata.
|
|
6
|
+
"""
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional, List, Dict, Any
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class NetworkInterface:
|
|
14
|
+
"""Represents a network interface on an agent"""
|
|
15
|
+
name: str
|
|
16
|
+
ip_addresses: List[str]
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'NetworkInterface':
|
|
20
|
+
return cls(
|
|
21
|
+
name=data.get('name', ''),
|
|
22
|
+
ip_addresses=data.get('ip_addresses', [])
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CloudflaredStatus:
|
|
28
|
+
"""Represents Cloudflared tunnel status"""
|
|
29
|
+
hostname: Optional[str] = None
|
|
30
|
+
tunnel_name: Optional[str] = None
|
|
31
|
+
authorized_users: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'CloudflaredStatus':
|
|
35
|
+
return cls(
|
|
36
|
+
hostname=data.get('hostname'),
|
|
37
|
+
tunnel_name=data.get('tunnel_name'),
|
|
38
|
+
authorized_users=data.get('authorized_users')
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class HealthCheck:
|
|
44
|
+
"""Represents agent health check data"""
|
|
45
|
+
cloudflared_status: Optional[CloudflaredStatus] = None
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'HealthCheck':
|
|
49
|
+
cf_data = data.get('cloudflared_status')
|
|
50
|
+
return cls(
|
|
51
|
+
cloudflared_status=CloudflaredStatus.from_dict(cf_data) if cf_data else None
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Agent:
|
|
57
|
+
"""Represents an Aegis agent"""
|
|
58
|
+
client_id: str = 'N/A'
|
|
59
|
+
hostname: str = 'Unknown'
|
|
60
|
+
fqdn: str = 'N/A'
|
|
61
|
+
os: str = 'unknown'
|
|
62
|
+
os_version: str = ''
|
|
63
|
+
architecture: str = 'Unknown'
|
|
64
|
+
last_seen_at: Optional[int] = None
|
|
65
|
+
network_interfaces: List[NetworkInterface] = None
|
|
66
|
+
health_check: Optional[HealthCheck] = None
|
|
67
|
+
key: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
def __post_init__(self):
|
|
70
|
+
if self.network_interfaces is None:
|
|
71
|
+
self.network_interfaces = []
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'Agent':
|
|
75
|
+
"""Create an Agent from dictionary data"""
|
|
76
|
+
network_interfaces = []
|
|
77
|
+
for iface_data in data.get('network_interfaces', []):
|
|
78
|
+
if isinstance(iface_data, dict):
|
|
79
|
+
network_interfaces.append(NetworkInterface.from_dict(iface_data))
|
|
80
|
+
|
|
81
|
+
health_data = data.get('health_check')
|
|
82
|
+
health_check = HealthCheck.from_dict(health_data) if health_data else None
|
|
83
|
+
|
|
84
|
+
return cls(
|
|
85
|
+
client_id=data.get('client_id', 'N/A'),
|
|
86
|
+
hostname=data.get('hostname', 'Unknown'),
|
|
87
|
+
fqdn=data.get('fqdn', 'N/A'),
|
|
88
|
+
os=data.get('os', 'unknown'),
|
|
89
|
+
os_version=data.get('os_version', ''),
|
|
90
|
+
architecture=data.get('architecture', 'Unknown'),
|
|
91
|
+
last_seen_at=data.get('last_seen_at'),
|
|
92
|
+
network_interfaces=network_interfaces,
|
|
93
|
+
health_check=health_check,
|
|
94
|
+
key=data.get('key')
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def has_tunnel(self) -> bool:
|
|
100
|
+
"""Check if agent has an active Cloudflare tunnel"""
|
|
101
|
+
return (self.health_check is not None and
|
|
102
|
+
self.health_check.cloudflared_status is not None and
|
|
103
|
+
self.health_check.cloudflared_status.hostname is not None)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def is_online(self) -> bool:
|
|
107
|
+
"""Check if agent is currently online (last seen within 60 seconds)"""
|
|
108
|
+
if not self.last_seen_at:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
current_time = datetime.now().timestamp()
|
|
112
|
+
last_seen_seconds = (self.last_seen_at / 1000000
|
|
113
|
+
if self.last_seen_at > 1000000000000
|
|
114
|
+
else self.last_seen_at)
|
|
115
|
+
|
|
116
|
+
return (current_time - last_seen_seconds) < 60
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def ip_addresses(self) -> List[str]:
|
|
120
|
+
"""Get all non-loopback IP addresses"""
|
|
121
|
+
ips = []
|
|
122
|
+
for iface in self.network_interfaces:
|
|
123
|
+
if iface.name != 'lo': # Skip loopback
|
|
124
|
+
ips.extend(iface.ip_addresses)
|
|
125
|
+
return [ip for ip in ips if ip] # Filter empty strings
|
|
126
|
+
|
|
127
|
+
def __str__(self) -> str:
|
|
128
|
+
"""Return a simple string representation of the agent"""
|
|
129
|
+
status = "🔗" if self.has_tunnel else "○"
|
|
130
|
+
return f"{status} {self.hostname} ({self.client_id})"
|
|
131
|
+
|
|
132
|
+
def to_detailed_string(self) -> str:
|
|
133
|
+
"""Return a detailed string representation of the agent"""
|
|
134
|
+
os_info = f"{self.os} {self.os_version}".strip()
|
|
135
|
+
|
|
136
|
+
lines = [
|
|
137
|
+
f"\n{self.hostname} ({self.client_id})",
|
|
138
|
+
f" OS: {os_info}",
|
|
139
|
+
f" Architecture: {self.architecture}",
|
|
140
|
+
f" FQDN: {self.fqdn}"
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
if self.has_tunnel:
|
|
144
|
+
cf_status = self.health_check.cloudflared_status
|
|
145
|
+
lines.append(f" Tunnel: {cf_status.tunnel_name}")
|
|
146
|
+
lines.append(f" Public hostname: {cf_status.hostname}")
|
|
147
|
+
if cf_status.authorized_users:
|
|
148
|
+
lines.append(f" Authorized users: {cf_status.authorized_users}")
|
|
149
|
+
else:
|
|
150
|
+
lines.append(" Tunnel: Not configured")
|
|
151
|
+
|
|
152
|
+
ips = self.ip_addresses
|
|
153
|
+
if ips:
|
|
154
|
+
lines.append(f" IP addresses: {', '.join(ips)}")
|
|
155
|
+
|
|
156
|
+
return '\n'.join(lines)
|
|
@@ -15,14 +15,11 @@ def integration_key(dns, name):
|
|
|
15
15
|
def risk_key(dns, name):
|
|
16
16
|
return f'#risk#{dns}#{name}'
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
def attribute_key(name, value, source_key):
|
|
20
19
|
return f'#attribute#{name}#{value}{source_key}'
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return f'#seed#{type}#{dns}'
|
|
25
|
-
|
|
21
|
+
def seed_asset_key(dns):
|
|
22
|
+
return f'#asset#{dns}#{dns}'
|
|
26
23
|
|
|
27
24
|
def preseed_key(type, title, value):
|
|
28
25
|
return f'#preseed#{type}#{title}#{value}'
|
|
@@ -32,6 +29,3 @@ def setting_key(name):
|
|
|
32
29
|
|
|
33
30
|
def configuration_key(name):
|
|
34
31
|
return f'#configuration#{name}'
|
|
35
|
-
|
|
36
|
-
def seed_status(type, status_code):
|
|
37
|
-
return f'{type}#{status_code}'
|
|
@@ -42,7 +42,7 @@ class TestAsset:
|
|
|
42
42
|
assert any([a['group'] == self.asset_dns for a in deleted_assets])
|
|
43
43
|
|
|
44
44
|
def test_add_ad_domain(self):
|
|
45
|
-
asset = self.sdk.assets.add(self.ad_domain_name, self.
|
|
45
|
+
asset = self.sdk.assets.add(self.ad_domain_name, self.ad_object_id, status=Asset.ACTIVE.value, surface='test-surface', type=Kind.ADDOMAIN.value)
|
|
46
46
|
assert asset['key'] == self.ad_domain_key
|
|
47
47
|
assert len(asset['attackSurface']) == 1
|
|
48
48
|
assert 'test-surface' in asset['attackSurface']
|
|
@@ -51,7 +51,7 @@ class TestAsset:
|
|
|
51
51
|
def test_get_ad_domain(self):
|
|
52
52
|
asset = self.sdk.assets.get(self.ad_domain_key)
|
|
53
53
|
assert asset['key'] == self.ad_domain_key
|
|
54
|
-
assert asset['
|
|
54
|
+
assert asset['domain'] == self.ad_domain_name
|
|
55
55
|
assert asset['status'] == Asset.ACTIVE.value
|
|
56
56
|
|
|
57
57
|
def test_list_ad_domain(self):
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from praetorian_cli.sdk.model.globals import Seed
|
|
4
|
-
from praetorian_cli.sdk.model.utils import seed_status
|
|
3
|
+
from praetorian_cli.sdk.model.globals import Seed, Kind
|
|
5
4
|
from praetorian_cli.sdk.test.utils import make_test_values, clean_test_entities, setup_chariot
|
|
6
5
|
|
|
7
6
|
|
|
@@ -12,30 +11,30 @@ class TestSeed:
|
|
|
12
11
|
self.sdk = setup_chariot()
|
|
13
12
|
make_test_values(self)
|
|
14
13
|
|
|
15
|
-
def
|
|
16
|
-
seed = self.sdk.seeds.add(self.
|
|
17
|
-
assert seed['key'] == self.
|
|
14
|
+
def test_add_asset_seed(self):
|
|
15
|
+
seed = self.sdk.seeds.add(dns=self.seed_asset_dns)
|
|
16
|
+
assert seed['key'] == self.seed_asset_key
|
|
18
17
|
|
|
19
18
|
def test_get_seed(self):
|
|
20
19
|
a = self.get_seed()
|
|
21
|
-
assert a['dns'] == self.
|
|
22
|
-
assert a['status'] ==
|
|
20
|
+
assert a['dns'] == self.seed_asset_dns
|
|
21
|
+
assert a['status'] == Seed.PENDING.value
|
|
23
22
|
|
|
24
23
|
def test_list_seed(self):
|
|
25
|
-
results, _ = self.sdk.seeds.list(
|
|
24
|
+
results, _ = self.sdk.seeds.list(Kind.ASSET.value, f"#asset#{self.seed_asset_dns}")
|
|
26
25
|
assert len(results) == 1
|
|
27
|
-
assert results[0]['dns'] == self.
|
|
26
|
+
assert results[0]['dns'] == self.seed_asset_dns
|
|
28
27
|
|
|
29
28
|
def test_update_seed(self):
|
|
30
|
-
self.sdk.seeds.update(self.
|
|
31
|
-
assert self.get_seed()['status'] ==
|
|
29
|
+
self.sdk.seeds.update(self.seed_asset_key, Seed.ACTIVE.value)
|
|
30
|
+
assert self.get_seed()['status'] == Seed.ACTIVE.value
|
|
32
31
|
|
|
33
32
|
def test_delete_seed(self):
|
|
34
|
-
self.sdk.seeds.delete(self.
|
|
35
|
-
assert self.sdk.seeds.get(self.
|
|
33
|
+
self.sdk.seeds.delete(self.seed_asset_key)
|
|
34
|
+
assert self.sdk.seeds.get(self.seed_asset_key)['status'] == Seed.DELETED.value
|
|
36
35
|
|
|
37
36
|
def get_seed(self):
|
|
38
|
-
return self.sdk.seeds.get(self.
|
|
37
|
+
return self.sdk.seeds.get(self.seed_asset_key)
|
|
39
38
|
|
|
40
39
|
def teardown_class(self):
|
|
41
40
|
clean_test_entities(self.sdk, self)
|
|
@@ -5,7 +5,6 @@ from subprocess import run
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
7
|
from praetorian_cli.sdk.model.globals import AddRisk, Asset, Risk, Seed, Preseed
|
|
8
|
-
from praetorian_cli.sdk.model.utils import seed_status
|
|
9
8
|
from praetorian_cli.sdk.test.utils import epoch_micro, random_ip, make_test_values, clean_test_entities, setup_chariot
|
|
10
9
|
|
|
11
10
|
|
|
@@ -21,11 +20,12 @@ class TestZCli:
|
|
|
21
20
|
self.verify(f'add asset -n {o.asset_name} -d {o.asset_dns}')
|
|
22
21
|
|
|
23
22
|
self.verify('list assets -p all', [o.asset_key])
|
|
24
|
-
self.verify(f'list assets -f "{o.asset_dns}"', [o.asset_key])
|
|
25
|
-
self.verify(f'list assets -f "{o.asset_dns}" -p first', [o.asset_key])
|
|
26
|
-
self.verify(f'list assets -f "{o.asset_dns}" -p all', [o.asset_key])
|
|
27
|
-
self.verify(f'list assets -f "{o.asset_dns}" -d', [o.asset_key, '"key"', '"data"'])
|
|
23
|
+
self.verify(f'list assets -f "#asset#{o.asset_dns}"', [o.asset_key])
|
|
24
|
+
self.verify(f'list assets -f "#asset#{o.asset_dns}" -p first', [o.asset_key])
|
|
25
|
+
self.verify(f'list assets -f "#asset#{o.asset_dns}" -p all', [o.asset_key])
|
|
26
|
+
self.verify(f'list assets -f "#asset#{o.asset_dns}" -d', [o.asset_key, '"key"', '"data"'])
|
|
28
27
|
|
|
28
|
+
self.verify(f'list assets -f "#asset#{epoch_micro()}"')
|
|
29
29
|
self.verify(f'list assets -f {epoch_micro()}')
|
|
30
30
|
|
|
31
31
|
self.verify(f'get asset "{o.asset_key}"', [o.asset_key, f'"status": "{Asset.ACTIVE.value}"'])
|
|
@@ -43,30 +43,28 @@ class TestZCli:
|
|
|
43
43
|
def test_seed_cli(self):
|
|
44
44
|
o = make_test_values(lambda: None)
|
|
45
45
|
|
|
46
|
-
self.verify(f'add seed
|
|
46
|
+
self.verify(f'add seed --field dns:{o.seed_asset_dns}')
|
|
47
47
|
|
|
48
|
-
self.verify('list seeds -p all', [o.
|
|
49
|
-
self.verify('list seeds -t
|
|
50
|
-
self.verify(f'list seeds -t
|
|
51
|
-
self.verify(f'list seeds -t
|
|
52
|
-
self.verify(f'list seeds -t
|
|
53
|
-
self.verify(f'list seeds -t
|
|
54
|
-
self.verify(f'list seeds -t
|
|
55
|
-
self.verify(f'list seeds -
|
|
56
|
-
self.verify(f'list seeds -f "{o.seed_dns}"', [],
|
|
57
|
-
["When the DNS filter is specified, you also need to specify the type of the filter"])
|
|
48
|
+
self.verify('list seeds -p all', [o.seed_asset_key])
|
|
49
|
+
self.verify('list seeds -t asset -p all', [o.seed_asset_key])
|
|
50
|
+
self.verify(f'list seeds -t asset -f "#asset#{o.seed_asset_dns}"', [o.seed_asset_key])
|
|
51
|
+
self.verify(f'list seeds -t asset -f "#asset#{o.seed_asset_dns}" -p first', [o.seed_asset_key])
|
|
52
|
+
self.verify(f'list seeds -t asset -f "#asset#{o.seed_asset_dns}" -p all', [o.seed_asset_key])
|
|
53
|
+
self.verify(f'list seeds -t asset -f "#asset#{o.seed_asset_dns}" -d', [o.seed_asset_dns, '"key"', '"data"'])
|
|
54
|
+
self.verify(f'list seeds -t notatype -f "#asset#{o.seed_asset_dns}"', [], ['Invalid seed type: notatype'])
|
|
55
|
+
self.verify(f'list seeds -f "#asset#{o.seed_asset_dns}"', [o.seed_asset_key])
|
|
58
56
|
|
|
59
|
-
self.verify(f'list seeds -t
|
|
57
|
+
self.verify(f'list seeds -t asset -f {epoch_micro()}')
|
|
60
58
|
|
|
61
|
-
self.verify(f'get seed "{o.
|
|
62
|
-
[o.
|
|
59
|
+
self.verify(f'get seed "{o.seed_asset_key}"',
|
|
60
|
+
[o.seed_asset_key, f'"status": "{Seed.PENDING.value}"'])
|
|
63
61
|
|
|
64
|
-
self.verify(f'update seed -s {Seed.ACTIVE.value} "{o.
|
|
65
|
-
self.verify(f'get seed "{o.
|
|
66
|
-
[o.
|
|
62
|
+
self.verify(f'update seed -s {Seed.ACTIVE.value} "{o.seed_asset_key}"')
|
|
63
|
+
self.verify(f'get seed "{o.seed_asset_key}"',
|
|
64
|
+
[o.seed_asset_key, f'"status": "{Seed.ACTIVE.value}"'])
|
|
67
65
|
|
|
68
|
-
self.verify(f'delete seed "{o.
|
|
69
|
-
self.verify(f'get seed "{o.
|
|
66
|
+
self.verify(f'delete seed "{o.seed_asset_key}"')
|
|
67
|
+
self.verify(f'get seed "{o.seed_asset_key}"', [f'"status": "{Seed.DELETED.value}"'])
|
|
70
68
|
|
|
71
69
|
clean_test_entities(self.sdk, o)
|
|
72
70
|
|