synapse 2.165.0__py311-none-any.whl → 2.166.0__py311-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.
Potentially problematic release.
This version of synapse might be problematic. Click here for more details.
- synapse/cmds/cortex.py +1 -6
- synapse/common.py +6 -0
- synapse/cortex.py +73 -56
- synapse/datamodel.py +32 -0
- synapse/lib/agenda.py +81 -51
- synapse/lib/ast.py +21 -23
- synapse/lib/base.py +0 -6
- synapse/lib/cell.py +13 -22
- synapse/lib/httpapi.py +1 -0
- synapse/lib/nexus.py +3 -2
- synapse/lib/schemas.py +2 -0
- synapse/lib/snap.py +50 -0
- synapse/lib/storm.py +19 -17
- synapse/lib/stormlib/aha.py +4 -1
- synapse/lib/stormlib/auth.py +11 -4
- synapse/lib/stormlib/cache.py +202 -0
- synapse/lib/stormlib/cortex.py +69 -7
- synapse/lib/stormlib/spooled.py +109 -0
- synapse/lib/stormtypes.py +43 -15
- synapse/lib/trigger.py +10 -12
- synapse/lib/types.py +1 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +12 -0
- synapse/models/inet.py +74 -2
- synapse/models/orgs.py +52 -8
- synapse/models/person.py +30 -11
- synapse/models/risk.py +44 -3
- synapse/telepath.py +114 -32
- synapse/tests/test_cortex.py +40 -6
- synapse/tests/test_datamodel.py +22 -0
- synapse/tests/test_lib_agenda.py +8 -1
- synapse/tests/test_lib_aha.py +18 -4
- synapse/tests/test_lib_storm.py +95 -4
- synapse/tests/test_lib_stormlib_cache.py +272 -0
- synapse/tests/test_lib_stormlib_cortex.py +71 -0
- synapse/tests/test_lib_stormlib_spooled.py +190 -0
- synapse/tests/test_lib_stormtypes.py +27 -4
- synapse/tests/test_model_inet.py +67 -0
- synapse/tests/test_model_risk.py +6 -0
- synapse/tests/test_telepath.py +30 -7
- synapse/tests/test_tools_modrole.py +81 -0
- synapse/tests/test_tools_moduser.py +105 -0
- synapse/tools/modrole.py +59 -7
- synapse/tools/moduser.py +78 -10
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/METADATA +2 -2
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/RECORD +49 -47
- synapse/lib/provenance.py +0 -111
- synapse/tests/test_lib_provenance.py +0 -37
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/LICENSE +0 -0
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/WHEEL +0 -0
- {synapse-2.165.0.dist-info → synapse-2.166.0.dist-info}/top_level.txt +0 -0
synapse/models/orgs.py
CHANGED
|
@@ -24,14 +24,27 @@ class OuModule(s_module.CoreModule):
|
|
|
24
24
|
}),
|
|
25
25
|
('ou:isic', ('str', {'regex': r'^[A-Z]([0-9]{2}[0-9]{0,2})?$'}), {
|
|
26
26
|
'doc': 'An International Standard Industrial Classification of All Economic Activities (ISIC) code.',
|
|
27
|
-
'ex': 'C1393',
|
|
28
|
-
|
|
27
|
+
'ex': 'C1393'}),
|
|
28
|
+
|
|
29
29
|
('ou:org', ('guid', {}), {
|
|
30
30
|
'doc': 'A GUID for a human organization such as a company or military unit.',
|
|
31
|
-
|
|
31
|
+
'display': {
|
|
32
|
+
'columns': (
|
|
33
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
34
|
+
{'type': 'prop', 'opts': {'name': 'names'}},
|
|
35
|
+
{'type': 'prop', 'opts': {'name': 'country:code'}},
|
|
36
|
+
),
|
|
37
|
+
}}),
|
|
38
|
+
|
|
32
39
|
('ou:team', ('guid', {}), {
|
|
33
40
|
'doc': 'A GUID for a team within an organization.',
|
|
34
|
-
|
|
41
|
+
'display': {
|
|
42
|
+
'columns': (
|
|
43
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
44
|
+
{'type': 'prop', 'opts': {'name': 'org::name'}},
|
|
45
|
+
),
|
|
46
|
+
}}),
|
|
47
|
+
|
|
35
48
|
('ou:orgtype', ('taxonomy', {}), {
|
|
36
49
|
'doc': 'An org type taxonomy.',
|
|
37
50
|
'interfaces': ('meta:taxonomy',),
|
|
@@ -49,6 +62,11 @@ class OuModule(s_module.CoreModule):
|
|
|
49
62
|
}),
|
|
50
63
|
('ou:industry', ('guid', {}), {
|
|
51
64
|
'doc': 'An industry classification type.',
|
|
65
|
+
'display': {
|
|
66
|
+
'columns': (
|
|
67
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
68
|
+
),
|
|
69
|
+
},
|
|
52
70
|
}),
|
|
53
71
|
('ou:industry:type:taxonomy', ('taxonomy', {}), {
|
|
54
72
|
'interfaces': ('meta:taxonomy',),
|
|
@@ -113,6 +131,12 @@ class OuModule(s_module.CoreModule):
|
|
|
113
131
|
}),
|
|
114
132
|
('ou:conference', ('guid', {}), {
|
|
115
133
|
'doc': 'A conference with a name and sponsoring org.',
|
|
134
|
+
'display': {
|
|
135
|
+
'columns': (
|
|
136
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
137
|
+
{'type': 'prop', 'opts': {'name': 'start'}},
|
|
138
|
+
),
|
|
139
|
+
},
|
|
116
140
|
}),
|
|
117
141
|
('ou:conference:attendee', ('comp', {'fields': (('conference', 'ou:conference'), ('person', 'ps:person'))}), {
|
|
118
142
|
'deprecated': True,
|
|
@@ -133,6 +157,11 @@ class OuModule(s_module.CoreModule):
|
|
|
133
157
|
}),
|
|
134
158
|
('ou:goal', ('guid', {}), {
|
|
135
159
|
'doc': 'An assessed or stated goal which may be abstract or org specific.',
|
|
160
|
+
'display': {
|
|
161
|
+
'columns': (
|
|
162
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
163
|
+
),
|
|
164
|
+
},
|
|
136
165
|
}),
|
|
137
166
|
('ou:goalname', ('str', {'lower': True, 'onespace': True}), {
|
|
138
167
|
'doc': 'A goal name.',
|
|
@@ -154,16 +183,31 @@ class OuModule(s_module.CoreModule):
|
|
|
154
183
|
|
|
155
184
|
('ou:campaign', ('guid', {}), {
|
|
156
185
|
'doc': "Represents an org's activity in pursuit of a goal.",
|
|
157
|
-
|
|
186
|
+
'display': {
|
|
187
|
+
'columns': (
|
|
188
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
189
|
+
{'type': 'prop', 'opts': {'name': 'names'}},
|
|
190
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
191
|
+
{'type': 'prop', 'opts': {'name': 'tag'}},
|
|
192
|
+
),
|
|
193
|
+
}}),
|
|
194
|
+
|
|
158
195
|
('ou:conflict', ('guid', {}), {
|
|
159
196
|
'doc': 'Represents a conflict where two or more campaigns have mutually exclusive goals.',
|
|
160
197
|
}),
|
|
161
198
|
('ou:contribution', ('guid', {}), {
|
|
162
|
-
'doc': 'Represents a specific instance of contributing material support to a campaign.',
|
|
163
|
-
|
|
199
|
+
'doc': 'Represents a specific instance of contributing material support to a campaign.'}),
|
|
200
|
+
|
|
164
201
|
('ou:technique', ('guid', {}), {
|
|
165
202
|
'doc': 'A specific technique used to achieve a goal.',
|
|
166
|
-
|
|
203
|
+
'display': {
|
|
204
|
+
'columns': (
|
|
205
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
206
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
207
|
+
{'type': 'prop', 'opts': {'name': 'tag'}},
|
|
208
|
+
),
|
|
209
|
+
}}),
|
|
210
|
+
|
|
167
211
|
('ou:technique:taxonomy', ('taxonomy', {}), {
|
|
168
212
|
'interfaces': ('meta:taxonomy',),
|
|
169
213
|
'doc': 'An analyst defined taxonomy to classify techniques in different disciplines.',
|
synapse/models/person.py
CHANGED
|
@@ -39,11 +39,19 @@ class PsModule(s_module.CoreModule):
|
|
|
39
39
|
('ps:persona:has', ('comp', {'fields': (('persona', 'ps:persona'), ('node', 'ndef'))}), {
|
|
40
40
|
'deprecated': True,
|
|
41
41
|
'doc': 'A persona owns, controls, or has exclusive use of an object or'
|
|
42
|
-
' resource, potentially during a specific period of time.'
|
|
43
|
-
|
|
42
|
+
' resource, potentially during a specific period of time.'}),
|
|
43
|
+
|
|
44
44
|
('ps:contact', ('guid', {}), {
|
|
45
45
|
'doc': 'A GUID for a contact info record.',
|
|
46
|
-
|
|
46
|
+
'display': {
|
|
47
|
+
'columns': (
|
|
48
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
49
|
+
{'type': 'prop', 'opts': {'name': 'type'}},
|
|
50
|
+
{'type': 'prop', 'opts': {'name': 'orgname'}},
|
|
51
|
+
{'type': 'prop', 'opts': {'name': 'email'}},
|
|
52
|
+
),
|
|
53
|
+
}}),
|
|
54
|
+
|
|
47
55
|
('ps:contact:type:taxonomy', ('taxonomy', {}), {
|
|
48
56
|
'interfaces': ('meta:taxonomy',),
|
|
49
57
|
'doc': 'A taxonomy of contact types.',
|
|
@@ -55,18 +63,29 @@ class PsModule(s_module.CoreModule):
|
|
|
55
63
|
'doc': "A GUID representing entry in a contact's work history.",
|
|
56
64
|
}),
|
|
57
65
|
('ps:vitals', ('guid', {}), {
|
|
58
|
-
'doc': 'Statistics and demographic data about a person or contact.',
|
|
59
|
-
|
|
66
|
+
'doc': 'Statistics and demographic data about a person or contact.'}),
|
|
67
|
+
|
|
60
68
|
('ps:skill', ('guid', {}), {
|
|
61
|
-
'doc': 'A specific skill which a person or organization may have.'
|
|
62
|
-
|
|
69
|
+
'doc': 'A specific skill which a person or organization may have.',
|
|
70
|
+
'display': {
|
|
71
|
+
'columns': (
|
|
72
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
73
|
+
{'type': 'prop', 'opts': {'name': 'type'}},
|
|
74
|
+
),
|
|
75
|
+
}}),
|
|
76
|
+
|
|
63
77
|
('ps:skill:type:taxonomy', ('taxonomy', {}), {
|
|
64
78
|
'interfaces': ('meta:taxonomy',),
|
|
65
|
-
'doc': 'A taxonomy of skill types.',
|
|
66
|
-
|
|
79
|
+
'doc': 'A taxonomy of skill types.'}),
|
|
80
|
+
|
|
67
81
|
('ps:proficiency', ('guid', {}), {
|
|
68
|
-
'doc': 'The assessment that a given contact possesses a specific skill.'
|
|
69
|
-
|
|
82
|
+
'doc': 'The assessment that a given contact possesses a specific skill.',
|
|
83
|
+
'display': {
|
|
84
|
+
'columns': (
|
|
85
|
+
{'type': 'prop', 'opts': {'name': 'contact::name'}},
|
|
86
|
+
{'type': 'prop', 'opts': {'name': 'skill::name'}},
|
|
87
|
+
),
|
|
88
|
+
}}),
|
|
70
89
|
),
|
|
71
90
|
'edges': (
|
|
72
91
|
(('ps:contact', 'has', None), {
|
synapse/models/risk.py
CHANGED
|
@@ -68,6 +68,14 @@ class RiskModule(s_module.CoreModule):
|
|
|
68
68
|
|
|
69
69
|
('risk:threat', ('guid', {}), {
|
|
70
70
|
'doc': 'A threat cluster or subgraph of threat activity, as reported by a specific organization.',
|
|
71
|
+
'display': {
|
|
72
|
+
'columns': (
|
|
73
|
+
{'type': 'prop', 'opts': {'name': 'org:name'}},
|
|
74
|
+
{'type': 'prop', 'opts': {'name': 'org:names'}},
|
|
75
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
76
|
+
{'type': 'prop', 'opts': {'name': 'tag'}},
|
|
77
|
+
),
|
|
78
|
+
},
|
|
71
79
|
}),
|
|
72
80
|
('risk:attack', ('guid', {}), {
|
|
73
81
|
'doc': 'An instance of an actor attacking a target.',
|
|
@@ -81,9 +89,22 @@ class RiskModule(s_module.CoreModule):
|
|
|
81
89
|
}),
|
|
82
90
|
('risk:compromise', ('guid', {}), {
|
|
83
91
|
'doc': 'An instance of a compromise and its aggregate impact.',
|
|
92
|
+
'display': {
|
|
93
|
+
'columns': (
|
|
94
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
95
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
96
|
+
),
|
|
97
|
+
},
|
|
84
98
|
}),
|
|
85
99
|
('risk:mitigation', ('guid', {}), {
|
|
86
100
|
'doc': 'A mitigation for a specific risk:vuln.',
|
|
101
|
+
'display': {
|
|
102
|
+
'columns': (
|
|
103
|
+
{'type': 'prop', 'opts': {'name': 'name'}},
|
|
104
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
105
|
+
{'type': 'prop', 'opts': {'name': 'tag'}},
|
|
106
|
+
),
|
|
107
|
+
},
|
|
87
108
|
}),
|
|
88
109
|
('risk:attacktype', ('taxonomy', {}), {
|
|
89
110
|
'doc': 'A taxonomy of attack types.',
|
|
@@ -104,6 +125,14 @@ class RiskModule(s_module.CoreModule):
|
|
|
104
125
|
}),
|
|
105
126
|
('risk:tool:software', ('guid', {}), {
|
|
106
127
|
'doc': 'A software tool used in threat activity, as reported by a specific organization.',
|
|
128
|
+
'display': {
|
|
129
|
+
'columns': (
|
|
130
|
+
{'type': 'prop', 'opts': {'name': 'soft:name'}},
|
|
131
|
+
{'type': 'prop', 'opts': {'name': 'soft:names'}},
|
|
132
|
+
{'type': 'prop', 'opts': {'name': 'reporter:name'}},
|
|
133
|
+
{'type': 'prop', 'opts': {'name': 'tag'}},
|
|
134
|
+
),
|
|
135
|
+
},
|
|
107
136
|
}),
|
|
108
137
|
|
|
109
138
|
('risk:alert:verdict:taxonomy', ('taxonomy', {}), {
|
|
@@ -128,19 +157,22 @@ class RiskModule(s_module.CoreModule):
|
|
|
128
157
|
('risk:extortion:type:taxonomy', ('taxonomy', {}), {
|
|
129
158
|
'interfaces': ('meta:taxonomy',),
|
|
130
159
|
'doc': 'A taxonomy of extortion event types.'}),
|
|
160
|
+
|
|
131
161
|
('risk:technique:masquerade', ('guid', {}), {
|
|
132
162
|
'doc': 'Represents the assessment that a node is designed to resemble another in order to mislead.'}),
|
|
133
163
|
),
|
|
134
164
|
'edges': (
|
|
135
165
|
# some explicit examples...
|
|
136
166
|
(('risk:attack', 'uses', 'ou:technique'), {
|
|
137
|
-
'doc': 'The
|
|
167
|
+
'doc': 'The attacker used the technique in the attack.'}),
|
|
138
168
|
(('risk:threat', 'uses', 'ou:technique'), {
|
|
139
169
|
'doc': 'The threat cluster uses the technique.'}),
|
|
140
170
|
(('risk:tool:software', 'uses', 'ou:technique'), {
|
|
141
171
|
'doc': 'The tool uses the technique.'}),
|
|
142
172
|
(('risk:compromise', 'uses', 'ou:technique'), {
|
|
143
|
-
'doc': 'The
|
|
173
|
+
'doc': 'The attacker used the technique in the compromise.'}),
|
|
174
|
+
(('risk:extortion', 'uses', 'ou:technique'), {
|
|
175
|
+
'doc': 'The attacker used the technique to extort the victim.'}),
|
|
144
176
|
|
|
145
177
|
(('risk:attack', 'uses', 'risk:vuln'), {
|
|
146
178
|
'doc': 'The attack used the vulnerability.'}),
|
|
@@ -936,12 +968,18 @@ class RiskModule(s_module.CoreModule):
|
|
|
936
968
|
('compromise', ('risk:compromise', {}), {
|
|
937
969
|
'doc': 'The compromise which allowed the leaker access to the information.'}),
|
|
938
970
|
|
|
971
|
+
('extortion', ('risk:extortion', {}), {
|
|
972
|
+
'doc': 'The extortion event which used the threat of the leak as leverage.'}),
|
|
973
|
+
|
|
939
974
|
('public', ('bool', {}), {
|
|
940
975
|
'doc': 'Set to true if the leaked information was made publicly available.'}),
|
|
941
976
|
|
|
942
977
|
('public:url', ('inet:url', {}), {
|
|
943
978
|
'doc': 'The URL where the leaked information was made publicly available.'}),
|
|
944
979
|
|
|
980
|
+
('size:bytes', ('int', {'min': 0}), {
|
|
981
|
+
'doc': 'The approximate uncompressed size of the total data leaked.'}),
|
|
982
|
+
|
|
945
983
|
)),
|
|
946
984
|
|
|
947
985
|
('risk:extortion:type:taxonomy', {}, ()),
|
|
@@ -963,6 +1001,9 @@ class RiskModule(s_module.CoreModule):
|
|
|
963
1001
|
('demanded', ('time', {}), {
|
|
964
1002
|
'doc': 'The time that the attacker made their demands.'}),
|
|
965
1003
|
|
|
1004
|
+
('deadline', ('time', {}), {
|
|
1005
|
+
'doc': 'The time that the demand must be met.'}),
|
|
1006
|
+
|
|
966
1007
|
('goal', ('ou:goal', {}), {
|
|
967
1008
|
'doc': 'The goal of the attacker in extorting the victim.'}),
|
|
968
1009
|
|
|
@@ -976,7 +1017,7 @@ class RiskModule(s_module.CoreModule):
|
|
|
976
1017
|
'doc': 'The extortion target identity.'}),
|
|
977
1018
|
|
|
978
1019
|
('success', ('bool', {}), {
|
|
979
|
-
'doc':
|
|
1020
|
+
'doc': "Set to true if the victim met the attacker's demands."}),
|
|
980
1021
|
|
|
981
1022
|
('enacted', ('bool', {}), {
|
|
982
1023
|
'doc': 'Set to true if attacker carried out the threat.'}),
|
synapse/telepath.py
CHANGED
|
@@ -135,25 +135,24 @@ def mergeAhaInfo(info0, info1):
|
|
|
135
135
|
|
|
136
136
|
return info0
|
|
137
137
|
|
|
138
|
-
async def open(url,
|
|
138
|
+
async def open(url, onlink=None):
|
|
139
139
|
'''
|
|
140
|
-
Open a new telepath
|
|
141
|
-
'''
|
|
142
|
-
# backward compatible support for a list of URLs or urlinfo dicts...
|
|
143
|
-
if isinstance(url, (tuple, list)): # pragma: no cover
|
|
144
|
-
return await Client.anit(url)
|
|
145
|
-
|
|
146
|
-
urlinfo = chopurl(url)
|
|
140
|
+
Open a new telepath ClientV2 object based on the given URL.
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
142
|
+
Args:
|
|
143
|
+
url (str): The URL to connect to.
|
|
144
|
+
onlink: An optional async callback function to run when connections are made.
|
|
151
145
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
Notes:
|
|
147
|
+
The onlink callback function has the call signature ``(proxy, urlinfo)``.
|
|
148
|
+
The proxy is the Telepath Proxy object.
|
|
149
|
+
The urlinfo is the parsed URL information used to create the proxy object.
|
|
150
|
+
The urlinfo structure may change between versions of Synapse.
|
|
155
151
|
|
|
156
|
-
|
|
152
|
+
Returns:
|
|
153
|
+
ClientV2: A ClientV2 object.
|
|
154
|
+
'''
|
|
155
|
+
return await ClientV2.anit(url, onlink=onlink)
|
|
157
156
|
|
|
158
157
|
async def _getAhaSvc(urlinfo, timeout=None):
|
|
159
158
|
|
|
@@ -832,7 +831,7 @@ class Proxy(s_base.Base):
|
|
|
832
831
|
|
|
833
832
|
mesg = await link.rx()
|
|
834
833
|
if mesg is None:
|
|
835
|
-
raise s_exc.LinkShutDown(mesg=
|
|
834
|
+
raise s_exc.LinkShutDown(mesg='Remote peer disconnected')
|
|
836
835
|
|
|
837
836
|
if mesg[0] != 't2:yield': # pragma: no cover
|
|
838
837
|
info = 'Telepath protocol violation: unexpected message received'
|
|
@@ -869,6 +868,8 @@ class Proxy(s_base.Base):
|
|
|
869
868
|
if self.sess is not None:
|
|
870
869
|
return await self.taskv2(todo, name=name)
|
|
871
870
|
|
|
871
|
+
s_common.deprecated('Telepath task with no session', curv='2.166.0')
|
|
872
|
+
|
|
872
873
|
task = Task()
|
|
873
874
|
|
|
874
875
|
mesg = ('task:init', {
|
|
@@ -980,21 +981,39 @@ class Proxy(s_base.Base):
|
|
|
980
981
|
setattr(self, name, meth)
|
|
981
982
|
return meth
|
|
982
983
|
|
|
983
|
-
class
|
|
984
|
+
class ClientV2(s_base.Base):
|
|
984
985
|
'''
|
|
985
986
|
A telepath client which:
|
|
986
987
|
* connects to multiple services
|
|
987
988
|
* distributes API calls across them
|
|
988
989
|
* receives topology updates from AHA
|
|
990
|
+
|
|
991
|
+
NOTE: This must co-exist with Client until we eliminate uses that
|
|
992
|
+
attempt to call telepath APIs directly from the Client rather
|
|
993
|
+
than awaiting a proxy()
|
|
989
994
|
'''
|
|
990
|
-
async def __anit__(self,
|
|
995
|
+
async def __anit__(self, urlinfo, onlink=None):
|
|
996
|
+
|
|
991
997
|
await s_base.Base.__anit__(self)
|
|
998
|
+
|
|
999
|
+
# some ugly stuff in order to be backward compatible...
|
|
1000
|
+
if not isinstance(urlinfo, (list, tuple)):
|
|
1001
|
+
urlinfo = (urlinfo,)
|
|
1002
|
+
|
|
1003
|
+
urlinfo = [chopurl(u) for u in urlinfo]
|
|
1004
|
+
|
|
1005
|
+
self.aha = None
|
|
1006
|
+
|
|
992
1007
|
self.clients = {}
|
|
993
1008
|
self.proxies = set()
|
|
994
1009
|
|
|
995
|
-
self.
|
|
996
|
-
|
|
997
|
-
self.
|
|
1010
|
+
self.poolname = None
|
|
1011
|
+
|
|
1012
|
+
self.onlink = onlink
|
|
1013
|
+
|
|
1014
|
+
self.booturls = urlinfo
|
|
1015
|
+
self.bootdeque = collections.deque()
|
|
1016
|
+
self.bootdeque.extend(self.booturls)
|
|
998
1017
|
|
|
999
1018
|
self.ready = asyncio.Event()
|
|
1000
1019
|
self.deque = collections.deque()
|
|
@@ -1003,7 +1022,6 @@ class Pool(s_base.Base):
|
|
|
1003
1022
|
'svc:add': self._onPoolSvcAdd,
|
|
1004
1023
|
'svc:del': self._onPoolSvcDel,
|
|
1005
1024
|
}
|
|
1006
|
-
self.schedCoro(self._toposync())
|
|
1007
1025
|
|
|
1008
1026
|
async def fini():
|
|
1009
1027
|
await self._shutDownPool()
|
|
@@ -1012,8 +1030,63 @@ class Pool(s_base.Base):
|
|
|
1012
1030
|
|
|
1013
1031
|
self.onfini(fini)
|
|
1014
1032
|
|
|
1033
|
+
self.schedCoro(self._initBootProxy())
|
|
1034
|
+
|
|
1035
|
+
def getNextBootUrl(self):
|
|
1036
|
+
if not self.bootdeque:
|
|
1037
|
+
self.bootdeque.extend(self.booturls)
|
|
1038
|
+
return self.bootdeque.popleft()
|
|
1039
|
+
|
|
1040
|
+
async def _initBootProxy(self):
|
|
1041
|
+
|
|
1042
|
+
lastlog = 0.0
|
|
1043
|
+
while not self.isfini:
|
|
1044
|
+
|
|
1045
|
+
urlinfo = self.getNextBootUrl()
|
|
1046
|
+
|
|
1047
|
+
try:
|
|
1048
|
+
|
|
1049
|
+
if urlinfo.get('scheme') == 'aha':
|
|
1050
|
+
|
|
1051
|
+
self.aha, svcinfo = await _getAhaSvc(urlinfo)
|
|
1052
|
+
|
|
1053
|
+
# if the service is a pool, enter pool mode and fire
|
|
1054
|
+
# the topography sync task to manage pool members.
|
|
1055
|
+
services = svcinfo.get('services')
|
|
1056
|
+
if services is not None:
|
|
1057
|
+
# we are an AHA pool!
|
|
1058
|
+
if self.poolname is None:
|
|
1059
|
+
self.poolname = svcinfo.get('name')
|
|
1060
|
+
self.schedCoro(self._toposync())
|
|
1061
|
+
return
|
|
1062
|
+
|
|
1063
|
+
# regular telepath client behavior
|
|
1064
|
+
proxy = await openinfo(urlinfo)
|
|
1065
|
+
await self._onPoolLink(proxy, urlinfo)
|
|
1066
|
+
|
|
1067
|
+
async def reconnect():
|
|
1068
|
+
if not self.isfini:
|
|
1069
|
+
self.schedCoro(self._initBootProxy())
|
|
1070
|
+
|
|
1071
|
+
proxy.onfini(reconnect)
|
|
1072
|
+
return
|
|
1073
|
+
|
|
1074
|
+
except Exception as e:
|
|
1075
|
+
|
|
1076
|
+
now = time.monotonic()
|
|
1077
|
+
if now > lastlog + 60.0: # don't logspam the disconnect message more than 1/min
|
|
1078
|
+
url = s_urlhelp.sanitizeUrl(zipurl(urlinfo))
|
|
1079
|
+
logger.exception(f'telepath clientv2 ({url}) encountered an error: {e}')
|
|
1080
|
+
lastlog = now
|
|
1081
|
+
|
|
1082
|
+
retrysleep = float(urlinfo.get('retrysleep', 0.2))
|
|
1083
|
+
await self.waitfini(timeout=retrysleep)
|
|
1084
|
+
|
|
1085
|
+
async def waitready(self, timeout=None):
|
|
1086
|
+
await s_common.wait_for(self.ready.wait(), timeout=timeout)
|
|
1087
|
+
|
|
1015
1088
|
def size(self):
|
|
1016
|
-
return len(self.
|
|
1089
|
+
return len(self.proxies)
|
|
1017
1090
|
|
|
1018
1091
|
async def _onPoolSvcAdd(self, mesg):
|
|
1019
1092
|
svcname = mesg[1].get('name')
|
|
@@ -1022,7 +1095,7 @@ class Pool(s_base.Base):
|
|
|
1022
1095
|
await oldc.fini()
|
|
1023
1096
|
|
|
1024
1097
|
urlinfo = {'scheme': 'aha', 'host': svcname, 'path': ''}
|
|
1025
|
-
self.clients[svcname] = await
|
|
1098
|
+
self.clients[svcname] = await ClientV2.anit(urlinfo, onlink=self._onPoolLink)
|
|
1026
1099
|
await self.fire('svc:add', **mesg[1])
|
|
1027
1100
|
|
|
1028
1101
|
async def _onPoolSvcDel(self, mesg):
|
|
@@ -1033,10 +1106,11 @@ class Pool(s_base.Base):
|
|
|
1033
1106
|
self.deque.clear()
|
|
1034
1107
|
await self.fire('svc:del', **mesg[1])
|
|
1035
1108
|
|
|
1036
|
-
async def _onPoolLink(self, proxy):
|
|
1109
|
+
async def _onPoolLink(self, proxy, urlinfo):
|
|
1037
1110
|
|
|
1038
1111
|
async def onfini():
|
|
1039
|
-
self.proxies
|
|
1112
|
+
if proxy in self.proxies:
|
|
1113
|
+
self.proxies.remove(proxy)
|
|
1040
1114
|
if proxy in self.deque:
|
|
1041
1115
|
self.deque.remove(proxy)
|
|
1042
1116
|
if not len(self.proxies):
|
|
@@ -1046,13 +1120,23 @@ class Pool(s_base.Base):
|
|
|
1046
1120
|
self.proxies.add(proxy)
|
|
1047
1121
|
self.ready.set()
|
|
1048
1122
|
|
|
1123
|
+
if self.onlink is not None:
|
|
1124
|
+
try:
|
|
1125
|
+
await self.onlink(proxy, urlinfo)
|
|
1126
|
+
except Exception as e:
|
|
1127
|
+
logger.exception(f'onlink: {self.onlink}')
|
|
1128
|
+
|
|
1049
1129
|
async def _shutDownPool(self):
|
|
1050
1130
|
# when we reconnect to our AHA service, we need to dump the current
|
|
1051
1131
|
# topology state and gather it again.
|
|
1052
|
-
for client in self.clients.values():
|
|
1132
|
+
for client in list(self.clients.values()):
|
|
1053
1133
|
await client.fini()
|
|
1054
1134
|
|
|
1135
|
+
for proxy in list(self.proxies):
|
|
1136
|
+
await proxy.fini()
|
|
1137
|
+
|
|
1055
1138
|
self.deque.clear()
|
|
1139
|
+
self.ready.clear()
|
|
1056
1140
|
self.clients.clear()
|
|
1057
1141
|
self.proxies.clear()
|
|
1058
1142
|
|
|
@@ -1065,16 +1149,14 @@ class Pool(s_base.Base):
|
|
|
1065
1149
|
|
|
1066
1150
|
while not self.isfini:
|
|
1067
1151
|
|
|
1068
|
-
poolname = self.ahasvc.get('name')
|
|
1069
|
-
|
|
1070
1152
|
try:
|
|
1071
1153
|
ahaproxy = await self.aha.proxy()
|
|
1072
1154
|
|
|
1073
1155
|
await reset()
|
|
1074
1156
|
|
|
1075
|
-
async for mesg in ahaproxy.iterPoolTopo(poolname):
|
|
1157
|
+
async for mesg in ahaproxy.iterPoolTopo(self.poolname):
|
|
1076
1158
|
hand = self.mesghands.get(mesg[0])
|
|
1077
|
-
if hand is None:
|
|
1159
|
+
if hand is None: # pragma: no cover
|
|
1078
1160
|
logger.warning(f'Unknown AHA pool topography message: {mesg}')
|
|
1079
1161
|
continue
|
|
1080
1162
|
|
|
@@ -1092,7 +1174,7 @@ class Pool(s_base.Base):
|
|
|
1092
1174
|
|
|
1093
1175
|
await self.ready.wait()
|
|
1094
1176
|
|
|
1095
|
-
if self.isfini:
|
|
1177
|
+
if self.isfini: # pragma: no cover
|
|
1096
1178
|
raise s_exc.IsFini()
|
|
1097
1179
|
|
|
1098
1180
|
if not self.deque:
|
synapse/tests/test_cortex.py
CHANGED
|
@@ -4564,10 +4564,9 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
4564
4564
|
await core1.addFeedData('syn.nodes', data)
|
|
4565
4565
|
self.len(1, await core1.nodes('test:int=8 -#test.12345'))
|
|
4566
4566
|
|
|
4567
|
-
# This tag does match regex
|
|
4568
4567
|
data = [(('test:int', 8), {'tags': {'test.1234': (None, None)}})]
|
|
4569
4568
|
await core1.addFeedData('syn.nodes', data)
|
|
4570
|
-
self.len(0, await core1.nodes('test:int=8 -#
|
|
4569
|
+
self.len(0, await core1.nodes('test:int=8 -#newtag.1234'))
|
|
4571
4570
|
|
|
4572
4571
|
core1.view.layers[0].readonly = True
|
|
4573
4572
|
await self.asyncraises(s_exc.IsReadOnly, core1.addFeedData('syn.nodes', data))
|
|
@@ -7809,6 +7808,9 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
7809
7808
|
self.stormHasNoWarnErr(msgs)
|
|
7810
7809
|
self.stormIsInPrint('AHA service (01.core...) added to service pool (pool00.loop.vertex.link)', msgs)
|
|
7811
7810
|
|
|
7811
|
+
msgs = await core00.stormlist('cortex.storm.pool.set newp')
|
|
7812
|
+
self.stormIsInErr(':// not found in [newp]', msgs)
|
|
7813
|
+
|
|
7812
7814
|
msgs = await core00.stormlist('cortex.storm.pool.set --connection-timeout 1 --sync-timeout 1 aha://pool00...')
|
|
7813
7815
|
self.stormHasNoWarnErr(msgs)
|
|
7814
7816
|
self.stormIsInPrint('Storm pool configuration set.', msgs)
|
|
@@ -7817,6 +7819,8 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
7817
7819
|
|
|
7818
7820
|
core00 = await base.enter_context(self.getTestCore(dirn=dirn00))
|
|
7819
7821
|
|
|
7822
|
+
await core00.stormpool.waitready(timeout=12)
|
|
7823
|
+
|
|
7820
7824
|
with self.getLoggerStream('synapse') as stream:
|
|
7821
7825
|
msgs = await alist(core00.storm('inet:asn=0'))
|
|
7822
7826
|
self.len(1, [m for m in msgs if m[0] == 'node'])
|
|
@@ -7906,28 +7910,28 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
7906
7910
|
|
|
7907
7911
|
stream.seek(0)
|
|
7908
7912
|
data = stream.read()
|
|
7909
|
-
self.isin('
|
|
7913
|
+
self.isin('Storm query mirror pool is empty, running query locally.', data)
|
|
7910
7914
|
|
|
7911
7915
|
with self.getLoggerStream('synapse') as stream:
|
|
7912
7916
|
self.true(await core00.callStorm('inet:asn=0 return($lib.true)'))
|
|
7913
7917
|
|
|
7914
7918
|
stream.seek(0)
|
|
7915
7919
|
data = stream.read()
|
|
7916
|
-
self.isin('
|
|
7920
|
+
self.isin('Storm query mirror pool is empty, running query locally.', data)
|
|
7917
7921
|
|
|
7918
7922
|
with self.getLoggerStream('synapse') as stream:
|
|
7919
7923
|
self.len(1, await alist(core00.exportStorm('inet:asn=0')))
|
|
7920
7924
|
|
|
7921
7925
|
stream.seek(0)
|
|
7922
7926
|
data = stream.read()
|
|
7923
|
-
self.isin('
|
|
7927
|
+
self.isin('Storm query mirror pool is empty, running query locally.', data)
|
|
7924
7928
|
|
|
7925
7929
|
with self.getLoggerStream('synapse') as stream:
|
|
7926
7930
|
self.eq(1, await core00.count('inet:asn=0'))
|
|
7927
7931
|
|
|
7928
7932
|
stream.seek(0)
|
|
7929
7933
|
data = stream.read()
|
|
7930
|
-
self.isin('
|
|
7934
|
+
self.isin('Storm query mirror pool is empty, running query locally.', data)
|
|
7931
7935
|
|
|
7932
7936
|
core01 = await base.enter_context(self.getTestCore(dirn=dirn01))
|
|
7933
7937
|
await core01.promote(graceful=True)
|
|
@@ -7984,3 +7988,33 @@ class CortexBasicTest(s_t_utils.SynTest):
|
|
|
7984
7988
|
|
|
7985
7989
|
msgs = await alist(core01.storm('inet:asn=0', opts={'mirror': False}))
|
|
7986
7990
|
self.len(1, [m for m in msgs if m[0] == 'node'])
|
|
7991
|
+
|
|
7992
|
+
async def test_cortex_authgate(self):
|
|
7993
|
+
# TODO - Remove this in 3.0.0
|
|
7994
|
+
with self.getTestDir() as dirn:
|
|
7995
|
+
|
|
7996
|
+
async with self.getTestCore(dirn=dirn) as core: # type: s_cortex.Cortex
|
|
7997
|
+
|
|
7998
|
+
unfo = await core.addUser('lowuser')
|
|
7999
|
+
lowuser = unfo.get('iden')
|
|
8000
|
+
|
|
8001
|
+
msgs = await core.stormlist('auth.user.addrule lowuser --gate cortex node')
|
|
8002
|
+
self.stormIsInWarn('Adding rule on the "cortex" authgate. This authgate is not used', msgs)
|
|
8003
|
+
msgs = await core.stormlist('auth.role.addrule all --gate cortex hehe')
|
|
8004
|
+
self.stormIsInWarn('Adding rule on the "cortex" authgate. This authgate is not used', msgs)
|
|
8005
|
+
|
|
8006
|
+
aslow = {'user': lowuser}
|
|
8007
|
+
|
|
8008
|
+
# The cortex authgate does nothing
|
|
8009
|
+
with self.raises(s_exc.AuthDeny) as cm:
|
|
8010
|
+
await core.nodes('[test:str=hello]', opts=aslow)
|
|
8011
|
+
|
|
8012
|
+
with self.getAsyncLoggerStream('synapse.cortex') as stream:
|
|
8013
|
+
async with self.getTestCore(dirn=dirn) as core: # type: s_cortex.Cortex
|
|
8014
|
+
# The cortex authgate still does nothing
|
|
8015
|
+
with self.raises(s_exc.AuthDeny) as cm:
|
|
8016
|
+
await core.nodes('[test:str=hello]', opts=aslow)
|
|
8017
|
+
stream.seek(0)
|
|
8018
|
+
buf = stream.read()
|
|
8019
|
+
self.isin('(lowuser) has a rule on the "cortex" authgate', buf)
|
|
8020
|
+
self.isin('(all) has a rule on the "cortex" authgate', buf)
|
synapse/tests/test_datamodel.py
CHANGED
|
@@ -41,6 +41,28 @@ class DeprecatedModel(s_module.CoreModule):
|
|
|
41
41
|
|
|
42
42
|
class DataModelTest(s_t_utils.SynTest):
|
|
43
43
|
|
|
44
|
+
async def test_datamodel_basics(self):
|
|
45
|
+
async with self.getTestCore() as core:
|
|
46
|
+
core.model.addType('woot:one', 'guid', {}, {
|
|
47
|
+
'display': {
|
|
48
|
+
'columns': (
|
|
49
|
+
{'type': 'newp', 'opts': {}},
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
with self.raises(s_exc.BadFormDef):
|
|
54
|
+
core.model.addForm('woot:one', {}, ())
|
|
55
|
+
|
|
56
|
+
core.model.addType('woot:two', 'guid', {}, {
|
|
57
|
+
'display': {
|
|
58
|
+
'columns': (
|
|
59
|
+
{'type': 'prop', 'opts': {'name': 'hehe'}},
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
with self.raises(s_exc.BadFormDef):
|
|
64
|
+
core.model.addForm('woot:two', {}, ())
|
|
65
|
+
|
|
44
66
|
async def test_datamodel_formname(self):
|
|
45
67
|
modl = s_datamodel.Model()
|
|
46
68
|
mods = (
|