@ethernity-cloud/sdk-js 1.6.1 → 1.7.0
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.
- package/README.md +44 -6
- package/cas/config.js +61 -3
- package/info.js +333 -324
- package/init.js +20 -4
- package/nodenithy/build/securelock/Dockerfile.tmpl +34 -32
- package/nodenithy/build/securelock/scripts/binary-fs-build.sh +7 -6
- package/nodenithy/build/securelock/src/securelock.js.tmpl +5 -2
- package/nodenithy/build.mjs +83 -71
- package/nodenithy/run/docker-compose-final.yml.tmpl +36 -31
- package/nodenithy/run/docker-compose.yml.tmpl +34 -45
- package/nodenithy/run/image_registry.js +396 -380
- package/nodenithy/run.js +42 -28
- package/package.json +5 -3
- package/publish.js +4 -2
- package/pynithy/build/securelock/Dockerfile.base.tpl +13 -0
- package/pynithy/build/securelock/Dockerfile.tpl +131 -0
- package/pynithy/build/securelock/scripts/binary-fs-build.sh +25 -34
- package/pynithy/build/securelock/scripts/pyfreeze.sh +32 -0
- package/pynithy/build/securelock/src/{pox.abi → abi/etnyImplementationV2.abi} +1489 -1489
- package/pynithy/build/securelock/src/abi/image_registry.abi +1 -0
- package/pynithy/build/securelock/src/ecld_state.py +906 -0
- package/pynithy/build/securelock/src/esr.abi +1 -0
- package/pynithy/build/securelock/src/esr_local.py +267 -0
- package/pynithy/build/securelock/src/esr_wallet.py +150 -0
- package/pynithy/build/securelock/src/etny_crypto.py +208 -167
- package/pynithy/build/securelock/src/etny_exec.py +385 -79
- package/pynithy/build/securelock/src/etny_exec_flask.py +535 -0
- package/pynithy/build/securelock/src/etny_exec_serv.py +353 -0
- package/pynithy/build/securelock/src/etny_session_exec.py +245 -0
- package/pynithy/build/securelock/src/get_sgx_report.so +0 -0
- package/pynithy/build/securelock/src/image_registry.abi +1 -1
- package/pynithy/build/securelock/src/key_generation.py +17 -17
- package/pynithy/build/securelock/src/local_api.py +189 -0
- package/pynithy/build/securelock/src/models.py +200 -176
- package/pynithy/build/securelock/src/securelock.py.tmpl +1037 -727
- package/pynithy/build/securelock/src/swift_stream_service.py +206 -186
- package/pynithy/build.mjs +142 -92
- package/pynithy/run/docker-compose-final.yml.tmpl +58 -51
- package/pynithy/run/docker-compose.yml.tmpl +62 -48
- package/pynithy/run/etny-securelock-test.yaml.tpl +4 -1
- package/pynithy/run/image_registry.js +396 -380
- package/pynithy/run.js +44 -28
- package/pynithy/src/serverless/Dockerfile.serverless +8 -0
- package/pynithy/src/serverless/requirements.txt +6 -0
- package/run.js +69 -28
- package/nodenithy/build/las/Dockerfile +0 -5
- package/pynithy/build/docker-compose.yml +0 -70
- package/pynithy/build/las/Dockerfile +0 -11
- package/pynithy/build/las/src/etny_entrypoint.sh +0 -6
- package/pynithy/build/las/src/las_entrypoint.sh +0 -26
- package/pynithy/build/scripts/binary-fs-build.sh +0 -28
- package/pynithy/build/securelock/Dockerfile.tmpl +0 -95
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# etny_exec.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
import secrets
|
|
10
|
+
import socket
|
|
11
|
+
import logging
|
|
12
|
+
from flask import Flask, request, jsonify
|
|
13
|
+
from functools import wraps
|
|
14
|
+
from flask_limiter import Limiter
|
|
15
|
+
from flask_limiter.util import get_remote_address
|
|
16
|
+
from pyngrok import ngrok
|
|
17
|
+
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
|
18
|
+
import importlib
|
|
19
|
+
|
|
20
|
+
# ===========================
|
|
21
|
+
# Backend Functions Loading
|
|
22
|
+
# ===========================
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import serverless.backend as backend
|
|
26
|
+
except ImportError:
|
|
27
|
+
backend = None
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
sdkFunctions = {}
|
|
31
|
+
if backend is not None:
|
|
32
|
+
for func in backend.__dict__.keys():
|
|
33
|
+
if func not in backend.__builtins__.keys() and func not in [
|
|
34
|
+
"__file__",
|
|
35
|
+
"__cached__",
|
|
36
|
+
"__builtins__",
|
|
37
|
+
]:
|
|
38
|
+
sdkFunctions.update({func: backend.__dict__[func]})
|
|
39
|
+
|
|
40
|
+
server_instance = None
|
|
41
|
+
|
|
42
|
+
# ===========================
|
|
43
|
+
# Flask and ngrok Setup
|
|
44
|
+
# ===========================
|
|
45
|
+
|
|
46
|
+
class EthernityEnclaveServer:
|
|
47
|
+
|
|
48
|
+
# Initialize the persistent execution context
|
|
49
|
+
globals_context = {}
|
|
50
|
+
|
|
51
|
+
# Initialize a reentrant threading lock for thread-safe access to the execution context
|
|
52
|
+
execution_lock = threading.RLock()
|
|
53
|
+
|
|
54
|
+
def __init__(self, local_port=5000):
|
|
55
|
+
self.api_key = self.generate_api_key()
|
|
56
|
+
self.local_port = local_port
|
|
57
|
+
self.ngrok_url = None
|
|
58
|
+
self.app = Flask(__name__)
|
|
59
|
+
self.setup_security()
|
|
60
|
+
self.setup_execution_environment()
|
|
61
|
+
self.setup_routes()
|
|
62
|
+
self.server_thread = threading.Thread(target=self.run_flask, daemon=True)
|
|
63
|
+
self.ngrok_thread = threading.Thread(target=self.start_ngrok, daemon=True)
|
|
64
|
+
|
|
65
|
+
def ___etny_result___(self, data):
|
|
66
|
+
return([0, data])
|
|
67
|
+
|
|
68
|
+
def generate_api_key(self):
|
|
69
|
+
"""Generates a secure random API key."""
|
|
70
|
+
return secrets.token_hex(16) # Generates a 32-character hexadecimal string
|
|
71
|
+
|
|
72
|
+
def setup_security(self):
|
|
73
|
+
"""Sets up authentication and rate limiting."""
|
|
74
|
+
# Authentication Decorator
|
|
75
|
+
def require_api_key(f):
|
|
76
|
+
@wraps(f)
|
|
77
|
+
def decorated(*args, **kwargs):
|
|
78
|
+
key = request.headers.get('x-api-key')
|
|
79
|
+
if not key or key != self.api_key:
|
|
80
|
+
return jsonify({"status": "error", "message": "Unauthorized: Invalid or missing API key."}), 401
|
|
81
|
+
return f(*args, **kwargs)
|
|
82
|
+
return decorated
|
|
83
|
+
|
|
84
|
+
# Rate Limiting using Flask-Limiter
|
|
85
|
+
self.limiter = Limiter(
|
|
86
|
+
key_func=get_remote_address,
|
|
87
|
+
default_limits=["200 per day", "50 per hour"]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.limiter.init_app(self.app)
|
|
91
|
+
|
|
92
|
+
# Attach the authentication decorator to the server instance
|
|
93
|
+
self.require_api_key = require_api_key
|
|
94
|
+
|
|
95
|
+
def setup_execution_environment(self):
|
|
96
|
+
"""Initializes the secure execution environment."""
|
|
97
|
+
# Initialize the persistent execution context
|
|
98
|
+
class GlobalContext:
|
|
99
|
+
def __init__(self):
|
|
100
|
+
self.context = {}
|
|
101
|
+
self.lock = threading.RLock()
|
|
102
|
+
|
|
103
|
+
def set(self, name, value, timeout=5):
|
|
104
|
+
acquired = self.lock.acquire(timeout=timeout)
|
|
105
|
+
if not acquired:
|
|
106
|
+
raise RuntimeError(f"Failed to acquire lock to set '{name}'.")
|
|
107
|
+
try:
|
|
108
|
+
self.context[name] = value
|
|
109
|
+
finally:
|
|
110
|
+
self.lock.release()
|
|
111
|
+
|
|
112
|
+
def get(self, name, timeout=5):
|
|
113
|
+
acquired = self.lock.acquire(timeout=timeout)
|
|
114
|
+
if not acquired:
|
|
115
|
+
raise RuntimeError(f"Failed to acquire lock to get '{name}'.")
|
|
116
|
+
try:
|
|
117
|
+
value = self.context.get(name)
|
|
118
|
+
return value
|
|
119
|
+
finally:
|
|
120
|
+
self.lock.release()
|
|
121
|
+
|
|
122
|
+
def update(self, updates, timeout=5):
|
|
123
|
+
acquired = self.lock.acquire(timeout=timeout)
|
|
124
|
+
if not acquired:
|
|
125
|
+
raise RuntimeError("Failed to acquire lock to update global context.")
|
|
126
|
+
try:
|
|
127
|
+
self.context.update(updates)
|
|
128
|
+
finally:
|
|
129
|
+
self.lock.release()
|
|
130
|
+
|
|
131
|
+
self.globals_context = GlobalContext()
|
|
132
|
+
|
|
133
|
+
# Define set_global and get_global functions
|
|
134
|
+
def set_global(name, value):
|
|
135
|
+
"""
|
|
136
|
+
Sets a global variable in globals_context.
|
|
137
|
+
Args:
|
|
138
|
+
name (str): The name of the global variable.
|
|
139
|
+
value: The value to assign to the global variable.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
self.globals_context.set(name, value)
|
|
143
|
+
except RuntimeError as e:
|
|
144
|
+
print("Error setting global variable")
|
|
145
|
+
# Depending on design, you might want to propagate the exception or handle it here
|
|
146
|
+
|
|
147
|
+
def get_global(name):
|
|
148
|
+
"""
|
|
149
|
+
Retrieves a global variable from globals_context.
|
|
150
|
+
Args:
|
|
151
|
+
name (str): The name of the global variable.
|
|
152
|
+
Returns:
|
|
153
|
+
The value of the global variable if it exists, else None.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
return self.globals_context.get(name)
|
|
157
|
+
except RuntimeError as e:
|
|
158
|
+
print("Error getting global variable")
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
self.set_global = set_global
|
|
162
|
+
self.get_global = get_global
|
|
163
|
+
|
|
164
|
+
# Load backend functions
|
|
165
|
+
self.load_backend_functions()
|
|
166
|
+
|
|
167
|
+
def load_backend_functions(self):
|
|
168
|
+
"""Dynamically load or reload backend functions from the 'serverless.backend' module."""
|
|
169
|
+
try:
|
|
170
|
+
import serverless.backend as backend
|
|
171
|
+
importlib.reload(backend) # Reload to get updated functions
|
|
172
|
+
|
|
173
|
+
# Inject set_global and get_global into the backend module's namespace
|
|
174
|
+
backend.set_global = self.set_global
|
|
175
|
+
backend.get_global = self.get_global
|
|
176
|
+
|
|
177
|
+
functions = {
|
|
178
|
+
func: getattr(backend, func)
|
|
179
|
+
for func in dir(backend)
|
|
180
|
+
if callable(getattr(backend, func)) and not func.startswith("__")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Update the global context with backend functions
|
|
184
|
+
self.globals_context.update(functions)
|
|
185
|
+
except ImportError:
|
|
186
|
+
print("backend module not found")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print("error loading backend functions")
|
|
189
|
+
|
|
190
|
+
def setup_routes(self):
|
|
191
|
+
"""Defines Flask routes."""
|
|
192
|
+
@self.app.route('/execute', methods=['POST'])
|
|
193
|
+
@self.require_api_key
|
|
194
|
+
@self.limiter.limit("10 per minute") # Additional rate limit for this endpoint
|
|
195
|
+
def execute():
|
|
196
|
+
"""
|
|
197
|
+
Executes user-provided Python code and returns the outputs.
|
|
198
|
+
"""
|
|
199
|
+
data = request.get_json()
|
|
200
|
+
if not data:
|
|
201
|
+
return jsonify({"status": "error", "message": "Invalid JSON payload."}), 400
|
|
202
|
+
|
|
203
|
+
payload = data.get('payload')
|
|
204
|
+
input_data = data.get('input_data', None)
|
|
205
|
+
|
|
206
|
+
if not payload:
|
|
207
|
+
return jsonify({"status": "error", "message": "No payload provided."}), 400
|
|
208
|
+
|
|
209
|
+
# Execute the payload
|
|
210
|
+
result = self.Exec(payload, input_data)
|
|
211
|
+
|
|
212
|
+
return jsonify(result)
|
|
213
|
+
|
|
214
|
+
@self.app.route('/reload_backend', methods=['POST'])
|
|
215
|
+
@self.require_api_key
|
|
216
|
+
def reload_backend():
|
|
217
|
+
"""
|
|
218
|
+
Reloads backend functions dynamically.
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
self.load_backend_functions()
|
|
222
|
+
return jsonify({"status": "success", "message": "Backend functions reloaded successfully."}), 200
|
|
223
|
+
except Exception as e:
|
|
224
|
+
return jsonify({"status": "error", "message": "Failed to reload backend functions."}), 500
|
|
225
|
+
|
|
226
|
+
@self.app.route('/health', methods=['GET'])
|
|
227
|
+
def health():
|
|
228
|
+
"""Health check endpoint."""
|
|
229
|
+
return jsonify({"status": "healthy"}), 200
|
|
230
|
+
|
|
231
|
+
@self.app.route('/list_functions', methods=['GET'])
|
|
232
|
+
@self.require_api_key
|
|
233
|
+
def list_functions():
|
|
234
|
+
"""
|
|
235
|
+
Lists all loaded backend functions.
|
|
236
|
+
"""
|
|
237
|
+
functions = {name: func.__doc__ for name, func in self.globals_context.context.items() if callable(func)}
|
|
238
|
+
return jsonify({"status": "success", "functions": functions}), 200
|
|
239
|
+
|
|
240
|
+
def run_flask(self):
|
|
241
|
+
"""Runs the Flask server."""
|
|
242
|
+
self.app.run(port=self.local_port, debug=False, use_reloader=False)
|
|
243
|
+
|
|
244
|
+
def start_ngrok(self):
|
|
245
|
+
"""Starts ngrok tunnel to forward the specified port."""
|
|
246
|
+
|
|
247
|
+
print("Starting ngrok")
|
|
248
|
+
try:
|
|
249
|
+
# Authenticate with ngrok
|
|
250
|
+
print("Setting authentication token")
|
|
251
|
+
auth_token = os.environ.get("ETNY_NGROK_AUTHTOKEN", "2orw7I8y15uYu4ajtlsGwimgAqK_6ke12fFHvBdkWkyYpEyf6")
|
|
252
|
+
print(auth_token)
|
|
253
|
+
ngrok.set_auth_token(auth_token)
|
|
254
|
+
print(ngrok)
|
|
255
|
+
# Forward the port with TLS
|
|
256
|
+
public_url = ngrok.connect(self.local_port, bind_tls=True)
|
|
257
|
+
|
|
258
|
+
print(public_url)
|
|
259
|
+
self.ngrok_url = public_url.public_url
|
|
260
|
+
print(f"ngrok tunnel established at {self.ngrok_url}")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print("Failed to start ngrok tunnel")
|
|
263
|
+
|
|
264
|
+
def initialize_application(self, payload_data, input_data = None):
|
|
265
|
+
"""
|
|
266
|
+
Initializes the EthernityEnclaveServer and executes the initial deployment function.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
health_url = f"{self.ngrok_url}/health"
|
|
270
|
+
headers = {"x-api-key": self.api_key}
|
|
271
|
+
|
|
272
|
+
for attempt in range(5):
|
|
273
|
+
try:
|
|
274
|
+
health_response = requests.get(health_url, headers=headers)
|
|
275
|
+
if health_response.status_code == 200 and health_response.json().get("status") == "healthy":
|
|
276
|
+
break
|
|
277
|
+
else:
|
|
278
|
+
print(f"Health check failed (Attempt {attempt + 1}/5). Retrying...")
|
|
279
|
+
except Exception as e:
|
|
280
|
+
print(f"Health check error (Attempt {attempt + 1}/5): {e}")
|
|
281
|
+
|
|
282
|
+
time.sleep(1)
|
|
283
|
+
|
|
284
|
+
if health_response.status_code != 200:
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
initial_deployment_payload = payload_data # Adjust as per your deploy function
|
|
288
|
+
|
|
289
|
+
# Step 5: Prepare the request headers
|
|
290
|
+
exec_headers = {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
"x-api-key": self.api_key
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Step 6: Prepare the JSON data
|
|
296
|
+
exec_data = {
|
|
297
|
+
"payload": initial_deployment_payload,
|
|
298
|
+
"input_data": None
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Step 7: Send the POST request to /execute
|
|
302
|
+
execute_url = f"{self.ngrok_url}/execute"
|
|
303
|
+
|
|
304
|
+
print("Executing initial deployment...")
|
|
305
|
+
try:
|
|
306
|
+
exec_response = requests.post(execute_url, headers=exec_headers, json=exec_data)
|
|
307
|
+
if exec_response.status_code == 200:
|
|
308
|
+
exec_result = exec_response.json()
|
|
309
|
+
else:
|
|
310
|
+
print("Response:", exec_response.json())
|
|
311
|
+
except Exception as e:
|
|
312
|
+
print(f"Error executing initial deployment: {e}")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def start(self, payload_data, input_data=None):
|
|
316
|
+
"""Starts the Flask server and ngrok tunnel."""
|
|
317
|
+
self.server_thread.start()
|
|
318
|
+
self.ngrok_thread.start()
|
|
319
|
+
|
|
320
|
+
while(self.ngrok_url == None):
|
|
321
|
+
time.sleep(0.1)
|
|
322
|
+
|
|
323
|
+
self.initialize_application(payload_data, input_data)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def Exec(self, payload_data, input_data=None):
|
|
327
|
+
"""
|
|
328
|
+
Executes the provided payload within the persistent execution context.
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
if payload_data:
|
|
332
|
+
local_context = {}
|
|
333
|
+
if input_data is not None:
|
|
334
|
+
self.globals_context.set("___etny_data_set___", input_data)
|
|
335
|
+
module = ast.parse(payload_data)
|
|
336
|
+
outputs = []
|
|
337
|
+
for node in module.body:
|
|
338
|
+
if isinstance(node, ast.Expr):
|
|
339
|
+
#print("node:", ast.unparse(node.value))
|
|
340
|
+
#print("ctx:", self.globals_context.context)
|
|
341
|
+
result = self.secure_exec_with_timeout("enty_expr_result = " + ast.unparse(node.value), globals_ctx=self.globals_context, locals_ctx=local_context, timeout=120)
|
|
342
|
+
|
|
343
|
+
#print("ctxa:", self.globals_context.context)
|
|
344
|
+
#print("output=", result['outputs']['enty_expr_result'])
|
|
345
|
+
|
|
346
|
+
outputs.append(result['outputs']['enty_expr_result'])
|
|
347
|
+
else:
|
|
348
|
+
# Handle statements if needed
|
|
349
|
+
#print("ctx:", self.globals_context.context)
|
|
350
|
+
self.secure_exec_with_timeout(ast.Module([node], type_ignores=[]), globals_ctx=self.globals_context, locals_ctx=local_context, timeout=120)
|
|
351
|
+
#print("ctxa:", self.globals_context.context)
|
|
352
|
+
|
|
353
|
+
result_object = {"outputs": outputs}
|
|
354
|
+
return self.___etny_result___(json.dumps(result_object, separators=(",", ":")))
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
return {"status": "error", "message": str(e)}
|
|
358
|
+
|
|
359
|
+
def is_json_serializable(self, obj):
|
|
360
|
+
"""Check if the object is JSON serializable."""
|
|
361
|
+
try:
|
|
362
|
+
json.dumps(obj)
|
|
363
|
+
return True
|
|
364
|
+
except (TypeError, OverflowError):
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
def secure_exec(self, code, globals_ctx=None, locals_ctx=None):
|
|
368
|
+
"""
|
|
369
|
+
Executes user-provided code in a restricted environment.
|
|
370
|
+
Only JSON-serializable outputs are returned.
|
|
371
|
+
Exposes set_global and get_global for dynamic global variable management.
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
byte_code = compile_restricted(code, '<string>', 'exec')
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
exec(byte_code, {
|
|
378
|
+
'__builtins__': safe_builtins,
|
|
379
|
+
'set_global': self.set_global, # Expose set_global
|
|
380
|
+
'get_global': self.get_global, # Expose get_global
|
|
381
|
+
**globals_ctx.context
|
|
382
|
+
}, locals_ctx)
|
|
383
|
+
|
|
384
|
+
excluded_vars = {'___etny_data_set___', '___etny_result___'}
|
|
385
|
+
|
|
386
|
+
outputs = {}
|
|
387
|
+
for var, value in locals_ctx.items():
|
|
388
|
+
if var not in excluded_vars and not var.startswith('__'):
|
|
389
|
+
if self.is_json_serializable(value):
|
|
390
|
+
outputs[var] = value
|
|
391
|
+
else:
|
|
392
|
+
outputs[var] = str(value)
|
|
393
|
+
|
|
394
|
+
return {"status": "success", "outputs": outputs}
|
|
395
|
+
except Exception as e:
|
|
396
|
+
return {"status": "error", "message": str(e)}
|
|
397
|
+
|
|
398
|
+
def secure_eval(self, code, globals_ctx=None, locals_ctx=None):
|
|
399
|
+
"""
|
|
400
|
+
Executes user-provided code in a restricted environment.
|
|
401
|
+
Only JSON-serializable outputs are returned.
|
|
402
|
+
Exposes set_global and get_global for dynamic global variable management.
|
|
403
|
+
"""
|
|
404
|
+
try:
|
|
405
|
+
print("eval:", code)
|
|
406
|
+
|
|
407
|
+
byte_code = compile_restricted(code, '<inline code>', 'eval')
|
|
408
|
+
|
|
409
|
+
result = eval(byte_code, {
|
|
410
|
+
'__builtins__': safe_builtins,
|
|
411
|
+
'return': self.set_global,
|
|
412
|
+
'set_global': self.set_global, # Expose set_global
|
|
413
|
+
'get_global': self.get_global, # Expose get_global
|
|
414
|
+
**globals_ctx.context
|
|
415
|
+
}, locals_ctx)
|
|
416
|
+
print("res:", result)
|
|
417
|
+
|
|
418
|
+
excluded_vars = {'___etny_data_set___', '___etny_result___'}
|
|
419
|
+
|
|
420
|
+
outputs.append(result)
|
|
421
|
+
|
|
422
|
+
return {"status": "success", "outputs": outputs}
|
|
423
|
+
except Exception as e:
|
|
424
|
+
return {"status": "error", "message": str(e)}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def secure_exec_with_timeout(self, code, globals_ctx=None, locals_ctx=None, timeout=15):
|
|
428
|
+
"""
|
|
429
|
+
Executes user-provided code in a restricted environment with a timeout.
|
|
430
|
+
Only JSON-serializable outputs are returned.
|
|
431
|
+
"""
|
|
432
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
433
|
+
future = executor.submit(self.secure_exec, code, globals_ctx, locals_ctx)
|
|
434
|
+
try:
|
|
435
|
+
result = future.result(timeout=timeout)
|
|
436
|
+
return result
|
|
437
|
+
except TimeoutError:
|
|
438
|
+
return {"status": "error", "message": "Code execution timed out."}
|
|
439
|
+
except Exception as e:
|
|
440
|
+
return {"status": "error", "message": str(e)}
|
|
441
|
+
|
|
442
|
+
def secure_eval_with_timeout(self, code, globals_ctx=None, locals_ctx=None, timeout=15):
|
|
443
|
+
"""
|
|
444
|
+
Executes user-provided code in a restricted environment with a timeout.
|
|
445
|
+
Only JSON-serializable outputs are returned.
|
|
446
|
+
"""
|
|
447
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
448
|
+
future = executor.submit(self.secure_eval, code, globals_ctx, locals_ctx)
|
|
449
|
+
try:
|
|
450
|
+
result = future.result(timeout=timeout)
|
|
451
|
+
print("rt:", result)
|
|
452
|
+
return result
|
|
453
|
+
except TimeoutError:
|
|
454
|
+
return {"status": "error", "message": "Code execution timed out."}
|
|
455
|
+
except Exception as e:
|
|
456
|
+
return {"status": "error", "message": str(e)}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def extract_variables_from_ast(self, payload_data):
|
|
460
|
+
"""Extracts variable names assigned in the payload."""
|
|
461
|
+
try:
|
|
462
|
+
parsed = ast.parse(payload_data)
|
|
463
|
+
variables = set()
|
|
464
|
+
for node in ast.walk(parsed):
|
|
465
|
+
if isinstance(node, ast.Assign):
|
|
466
|
+
for target in node.targets:
|
|
467
|
+
if isinstance(target, ast.Name):
|
|
468
|
+
variables.add(target.id)
|
|
469
|
+
elif isinstance(node, (ast.Tuple, ast.List)):
|
|
470
|
+
for elt in node.elts:
|
|
471
|
+
if isinstance(elt, ast.Name):
|
|
472
|
+
variables.add(elt.id)
|
|
473
|
+
return variables
|
|
474
|
+
except Exception as e:
|
|
475
|
+
return set()
|
|
476
|
+
|
|
477
|
+
def execute_server_v4(payload_data, input_data=None, globals=None, locals=None):
|
|
478
|
+
"""
|
|
479
|
+
The main ExecServer function that sets up the server.
|
|
480
|
+
It does not execute any tasks directly.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
payload_data (str): The Python code to execute (unused in this setup).
|
|
484
|
+
input_data (any): Optional input data (unused in this setup).
|
|
485
|
+
globals (dict): Global variables.
|
|
486
|
+
locals (dict): Local variables.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
dict: Information about the server setup.
|
|
490
|
+
"""
|
|
491
|
+
global server_instance
|
|
492
|
+
server_lock = threading.Lock()
|
|
493
|
+
with server_lock:
|
|
494
|
+
print("SL")
|
|
495
|
+
if server_instance is None:
|
|
496
|
+
# Initialize and start the EthernityEnclaveServer
|
|
497
|
+
print("SI")
|
|
498
|
+
server_instance = EthernityEnclaveServer(local_port=5000)
|
|
499
|
+
server_instance.start(payload_data, input_data)
|
|
500
|
+
# Wait briefly to ensure ngrok tunnel is established
|
|
501
|
+
time.sleep(2)
|
|
502
|
+
if not server_instance.ngrok_url:
|
|
503
|
+
return {
|
|
504
|
+
"status": "error",
|
|
505
|
+
"message": "Failed to establish ngrok tunnel."
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
# Return server details
|
|
509
|
+
return [ 0, f"{server_instance.ngrok_url}:{server_instance.api_key}"]
|
|
510
|
+
|
|
511
|
+
# ===========================
|
|
512
|
+
# Server Management
|
|
513
|
+
# ===========================
|
|
514
|
+
|
|
515
|
+
def execute_task_v4(payload_data, input_data=None):
|
|
516
|
+
"""
|
|
517
|
+
Executes the task by calling the ExecFunction.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
payload_data (str): The Python code to execute.
|
|
521
|
+
input_data (any): Optional input data.
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
dict: Result of the execution.
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
global server_instance
|
|
528
|
+
if server_instance is None:
|
|
529
|
+
# Initialize and start the EthernityEnclaveServer
|
|
530
|
+
server_instance = EthernityEnclaveServer(local_port=5000)
|
|
531
|
+
|
|
532
|
+
return server_instance.Exec(
|
|
533
|
+
payload_data,
|
|
534
|
+
input_data,
|
|
535
|
+
)
|