pirogue-admin-client 2.0.4__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.
@@ -0,0 +1,101 @@
1
+ from cryptography import x509
2
+ from enum import Enum
3
+ from pathlib import Path
4
+
5
+ import grpc
6
+ import yaml
7
+ from cryptography.x509.oid import NameOID
8
+
9
+ from pirogue_admin_api import PIROGUE_ADMIN_TCP_PORT
10
+ from pirogue_admin_client.adapters import SystemAdapter, NetworkAdapter, ServicesAdapter, access_token_call_credentials
11
+
12
+ ADMIN_VAR_DIR = '/var/lib/pirogue/admin'
13
+ USERLAND_CLIENT_CONFIG_FILENAME = '.pirogue-admin-client.conf'
14
+
15
+
16
+ class PirogueAdminClientAdapter(SystemAdapter, NetworkAdapter, ServicesAdapter):
17
+ _host: str
18
+ _port: int
19
+ _token: str
20
+ _certificate: str
21
+
22
+ def __init__(self, host: str = None, port: int = None,
23
+ token: str = None, certificate: str = None):
24
+ self._host = host
25
+ self._port = port
26
+ self._token = token
27
+ self._certificate = certificate
28
+
29
+ self._local_pirogue_client_config_path = Path(ADMIN_VAR_DIR, 'client.yaml')
30
+ self._userland_client_config_path = Path(Path.home(), USERLAND_CLIENT_CONFIG_FILENAME)
31
+
32
+ # _use_tls will be updated by _load_configuration
33
+ self._use_tls = False
34
+ self._load_configuration()
35
+
36
+ chan_str = f'{self._host}:{self._port}'
37
+ token_call_injector = access_token_call_credentials(self._token)
38
+
39
+ secure_channel = grpc.ssl_channel_credentials(
40
+ str.encode(self._certificate) if (self._certificate is not None and
41
+ self._certificate != 'public') else None)
42
+ local_channel = grpc.local_channel_credentials(grpc.LocalConnectionType.LOCAL_TCP)
43
+
44
+ composite_credentials = grpc.composite_channel_credentials(
45
+ secure_channel if self._use_tls else local_channel,
46
+ token_call_injector,
47
+ )
48
+
49
+ options = ()
50
+ if self._certificate is not None and self._certificate != 'public':
51
+ cert_decoded = x509.load_pem_x509_certificate(str.encode(self._certificate))
52
+ (common_name,) = cert_decoded.subject.get_attributes_for_oid(NameOID.COMMON_NAME)
53
+ cn_target = common_name.value
54
+ options = (('grpc.ssl_target_name_override', f'{cn_target}',),)
55
+
56
+ channel = grpc.secure_channel(chan_str, composite_credentials, options)
57
+
58
+ super(PirogueAdminClientAdapter, self).__init__(channel)
59
+
60
+ def _load_configuration(self):
61
+ loaded_config = None
62
+
63
+ if self._local_pirogue_client_config_path.exists():
64
+ loaded_config = yaml.safe_load(self._local_pirogue_client_config_path.read_text())
65
+ elif self._userland_client_config_path.exists():
66
+ loaded_config = yaml.safe_load(self._userland_client_config_path.read_text())
67
+
68
+ if isinstance(loaded_config, dict): # Prevents existing but empty file
69
+ self._host = loaded_config['host'] if self._host is None else self._host
70
+ self._port = loaded_config['port'] if self._port is None else self._port
71
+ self._token = loaded_config['token'] if self._token is None else self._token
72
+ # Support previous existing version
73
+ # where 'certificate' key could not be present
74
+ if 'certificate' in loaded_config:
75
+ if self._certificate is None:
76
+ self._certificate = loaded_config['certificate']
77
+
78
+ self._host = 'localhost' if self._host is None else self._host
79
+ self._port = PIROGUE_ADMIN_TCP_PORT if self._port is None else self._port
80
+
81
+ self._use_tls = self._host not in ['localhost', '127.0.0.1', 'ip6-localhost']
82
+
83
+ if self._host in (None, ''):
84
+ raise RuntimeError("Can't connect without host parameter")
85
+ if self._port in (None, '', 0):
86
+ raise RuntimeError("Can't connect without port parameter")
87
+ if self._token in (None, ''):
88
+ raise RuntimeError("Can't connect without token parameter")
89
+
90
+ def save_configuration(self):
91
+ if self._local_pirogue_client_config_path.exists():
92
+ raise RuntimeError('Cannot save configuration on PiRogue, this may interfere with daemon configuration')
93
+ else:
94
+ with open(self._userland_client_config_path, 'w') as out_fs:
95
+ yaml.safe_dump({
96
+ 'host': self._host,
97
+ 'port': self._port,
98
+ 'token': self._token,
99
+ 'certificate': 'public' if self._certificate is None else self._certificate,
100
+ }, out_fs)
101
+
@@ -0,0 +1,273 @@
1
+ import functools
2
+ import logging
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Optional, Callable
6
+
7
+ import grpc
8
+ import yaml
9
+ from google.protobuf import empty_pb2
10
+ from google.protobuf.json_format import MessageToDict, ParseDict
11
+ from google.protobuf.wrappers_pb2 import Int32Value, UInt32Value, StringValue
12
+
13
+ from pirogue_admin_api import system_pb2, system_pb2_grpc
14
+ from pirogue_admin_api import services_pb2, services_pb2_grpc
15
+ from pirogue_admin_api import network_pb2, network_pb2_grpc
16
+
17
+ from pirogue_admin_api import (
18
+ PIROGUE_ADMIN_AUTH_HEADER, PIROGUE_ADMIN_AUTH_SCHEME,
19
+ PIROGUE_ADMIN_TCP_PORT)
20
+ from pirogue_admin_api.network_pb2 import WifiConfiguration, VPNPeerAddRequest, ClosePortRequest, IsolatedPort, PublicAccessRequest
21
+ from pirogue_admin_api.services_pb2 import DashboardConfiguration, SuricataRulesSource
22
+ from pirogue_admin_client.types import OperatingMode
23
+
24
+ EMPTY = empty_pb2.Empty()
25
+
26
+
27
+ class NoPiRogueAdminConnection(BaseException):
28
+ """"""
29
+
30
+
31
+ class ApplyConfigurationError(BaseException):
32
+ """"""
33
+
34
+
35
+ def _inject_token_request(callback: grpc.AuthMetadataPluginCallback,
36
+ token: Optional[str], error: Optional[Exception]):
37
+ metadata = ((PIROGUE_ADMIN_AUTH_HEADER, '%s %s' % (PIROGUE_ADMIN_AUTH_SCHEME, token)),)
38
+ callback(metadata, error)
39
+
40
+
41
+ class TokenAuthMetadataPlugin(grpc.AuthMetadataPlugin):
42
+ """Metadata wrapper for raw access token credentials."""
43
+ _token: str
44
+
45
+ def __init__(self, access_token: str):
46
+ self._token = access_token
47
+
48
+ def __call__(self, context: grpc.AuthMetadataContext,
49
+ callback: grpc.AuthMetadataPluginCallback):
50
+ _inject_token_request(callback, self._token, None)
51
+
52
+
53
+ def access_token_call_credentials(access_token):
54
+ """Construct CallCredentials from an access token.
55
+
56
+ Args:
57
+ access_token: A string to place directly in the http request
58
+ authorization header, for example
59
+ "authorization: Token <access_token>".
60
+
61
+ Returns:
62
+ A CallCredentials.
63
+ """
64
+ return grpc.metadata_call_credentials(
65
+ TokenAuthMetadataPlugin(access_token), None)
66
+
67
+
68
+ class BaseAdapter:
69
+
70
+ def __init__(self, channel):
71
+ pass
72
+
73
+
74
+ class SystemAdapter(BaseAdapter):
75
+ _stub_system: system_pb2_grpc.SystemStub
76
+
77
+ def __init__(self, channel):
78
+ super(SystemAdapter, self).__init__(channel)
79
+ self._stub_system = system_pb2_grpc.SystemStub(channel)
80
+
81
+ def get_configuration_tree(self):
82
+ answer = self._stub_system.GetConfigurationTree(EMPTY)
83
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
84
+ return answer
85
+
86
+ def get_configuration(self):
87
+ answer = self._stub_system.GetConfiguration(EMPTY)
88
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
89
+ if 'variables' in answer:
90
+ answer = answer['variables']
91
+ return answer
92
+
93
+ def get_operating_mode(self):
94
+ answer = self._stub_system.GetOperatingMode(EMPTY)
95
+ return OperatingMode(answer.mode)
96
+
97
+ def get_status(self):
98
+ answer = self._stub_system.GetStatus(EMPTY)
99
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
100
+ return answer
101
+
102
+ def get_packages_info(self):
103
+ answer = self._stub_system.GetPackagesInfo(EMPTY)
104
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
105
+ if 'packages' in answer:
106
+ answer = answer['packages']
107
+ return answer
108
+
109
+ def get_hostname(self):
110
+ answer = self._stub_system.GetHostname(EMPTY)
111
+ answer = MessageToDict(answer)
112
+ return answer
113
+
114
+ def set_hostname(self, hostname: str):
115
+ answer = self._stub_system.SetHostname(StringValue(value=hostname))
116
+
117
+ def get_locale(self):
118
+ answer = self._stub_system.GetLocale(EMPTY)
119
+ answer = MessageToDict(answer)
120
+ return answer
121
+
122
+ def set_locale(self, locale: str):
123
+ answer = self._stub_system.SetLocale(StringValue(value=locale))
124
+
125
+ def get_timezone(self):
126
+ answer = self._stub_system.GetTimezone(EMPTY)
127
+ answer = MessageToDict(answer)
128
+ return answer
129
+
130
+ def set_timezone(self, timezone: str):
131
+ answer = self._stub_system.SetTimezone(StringValue(value=timezone))
132
+
133
+ def list_connected_devices(self):
134
+ answer = self._stub_system.ListConnectedDevices(EMPTY)
135
+ answer = MessageToDict(answer)
136
+ return answer
137
+
138
+
139
+ class NetworkAdapter(BaseAdapter):
140
+ _stub_network: network_pb2_grpc.NetworkStub
141
+
142
+ def __init__(self, channel):
143
+ super(NetworkAdapter, self).__init__(channel)
144
+ self._stub_network = network_pb2_grpc.NetworkStub(channel)
145
+
146
+ def get_wifi_configuration(self):
147
+ answer = self._stub_network.GetWifiConfiguration(EMPTY)
148
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
149
+ return answer
150
+
151
+ def set_wifi_configuration(self, ssid: str = None, passphrase: str = None, country_code: str = None):
152
+ if ssid is None and passphrase is None and country_code is None:
153
+ raise ValueError('please provide at least one of ssid, passphrase or country_code')
154
+ message = WifiConfiguration()
155
+ if ssid:
156
+ message.ssid = ssid
157
+ if passphrase:
158
+ message.passphrase = passphrase
159
+ if country_code:
160
+ message.country_code = country_code
161
+ logging.debug(f'set_wifi_configuration({message})')
162
+ answer = self._stub_network.SetWifiConfiguration(message)
163
+
164
+ def list_vpn_peers(self):
165
+ answer = self._stub_network.ListVPNPeers(EMPTY)
166
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
167
+ if 'peers' in answer:
168
+ answer = answer['peers']
169
+ return answer
170
+
171
+ def get_vpn_peer(self, idx: int):
172
+ idx_param = Int32Value()
173
+ idx_param.value = int(idx)
174
+ answer = self._stub_network.GetVPNPeer(idx_param)
175
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
176
+ return answer
177
+
178
+ def get_vpn_peer_config(self, idx: int):
179
+ idx_param = Int32Value()
180
+ idx_param.value = int(idx)
181
+ answer = self._stub_network.GetVPNPeerConfig(idx_param)
182
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
183
+ return answer
184
+
185
+ def add_vpn_peer(self, comment: str = None, public_key: str = None):
186
+ add_request = VPNPeerAddRequest(comment=comment, public_key=public_key)
187
+ answer = self._stub_network.AddVPNPeer(add_request)
188
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
189
+ return answer
190
+
191
+ def delete_vpn_peer(self, idx: int):
192
+ idx_param = Int32Value()
193
+ idx_param.value = int(idx)
194
+ answer = self._stub_network.DeleteVPNPeer(idx_param)
195
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
196
+ return answer
197
+
198
+ def reset_administration_token(self):
199
+ answer = self._stub_network.ResetAdministrationToken(EMPTY)
200
+ answer = MessageToDict(answer)
201
+ return answer
202
+
203
+ def get_administration_token(self):
204
+ answer = self._stub_network.GetAdministrationToken(EMPTY)
205
+ answer = MessageToDict(answer)
206
+ return answer
207
+
208
+ def get_administration_certificate(self):
209
+ answer = self._stub_network.GetAdministrationCertificate(EMPTY)
210
+ answer = MessageToDict(answer)
211
+ return answer
212
+
213
+ def get_administration_clis(self):
214
+ answer = self._stub_network.GetAdministrationCLIs(EMPTY)
215
+ answer = MessageToDict(answer)
216
+ return answer
217
+
218
+ def enable_external_public_access(self, domain: str, email: str):
219
+ request = PublicAccessRequest(domain=domain, email=email)
220
+ answer = self._stub_network.EnableExternalPublicAccess(request)
221
+
222
+ def disable_external_public_access(self):
223
+ answer = self._stub_network.DisableExternalPublicAccess(EMPTY)
224
+
225
+ def list_isolated_open_ports(self):
226
+ answer = self._stub_network.ListIsolatedOpenPorts(EMPTY)
227
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
228
+ if 'ports' in answer:
229
+ answer = answer['ports']
230
+ return answer
231
+
232
+ def open_isolated_port(self, incoming_port: int, outgoing_port: int = None):
233
+ request = IsolatedPort(port=int(incoming_port))
234
+ if outgoing_port:
235
+ request.destination_port = int(outgoing_port)
236
+ answer = self._stub_network.OpenIsolatedPort(request)
237
+
238
+ def close_isolated_port(self, incoming_port: int = None):
239
+ request = ClosePortRequest()
240
+ if incoming_port:
241
+ request.port = int(incoming_port)
242
+ answer = self._stub_network.CloseIsolatedPort(request)
243
+
244
+
245
+ class ServicesAdapter(BaseAdapter):
246
+ _stub_services: services_pb2_grpc.ServicesStub
247
+
248
+ def __init__(self, channel):
249
+ super(ServicesAdapter, self).__init__(channel)
250
+ self._stub_services = services_pb2_grpc.ServicesStub(channel)
251
+
252
+ def set_dashboard_configuration(self, password: str):
253
+ request = DashboardConfiguration(password=password)
254
+ answer = self._stub_services.SetDashboardConfiguration(request)
255
+
256
+ def get_dashboard_configuration(self):
257
+ answer = self._stub_services.GetDashboardConfiguration(EMPTY)
258
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
259
+ return answer
260
+
261
+ def list_suricata_rules_sources(self):
262
+ answer = self._stub_services.ListSuricataRulesSources(EMPTY)
263
+ answer = MessageToDict(answer, preserving_proto_field_name=True)
264
+ if 'sources' in answer:
265
+ answer = answer['sources']
266
+ return answer
267
+
268
+ def add_suricata_rules_source(self, name: str, url: str):
269
+ add_request = SuricataRulesSource(name=name, url=url)
270
+ answer = self._stub_services.AddSuricataRulesSource(add_request)
271
+
272
+ def delete_suricata_rules_source(self, name: str):
273
+ answer = self._stub_services.DeleteSuricataRulesSource(StringValue(value=name))