Title: Upgrade to FutureOSS v1.1.0 with enterprise-grade security and deployment features
Key features implemented: - New RELEASE_v1.1.0.md with comprehensive release notes for security upgrades and new features - New firewall.py plugin implementing dynamic IP filtering, port management, and attack detection - New frp_proxy.py plugin for FRP-based internal network tunneling and proxy services - New ftp_server.py plugin providing secure file transfer with user management and access control - New multi_lang_deploy.py orchestrator supporting automated detection and deployment of Python/Node.js/Go/Java/PHP projects - New ops_toolbox.py with backup/recovery, health checks, and resource quota management - New security_gateway.py with API rate limiting, JWT authentication, audit logging, and circuit breaker protection - New HTML5/CSS3/JS-based webui replacing PHP templates with modern responsive design and real-time metrics - New manifest.json files for all plugins adding configuration schemas and dependency declarations - Updated .gitignore with refined ignore patterns for development environments - Modified core plugin manifests to include internationalization dependencies and enhanced configurations - Removed legacy PHP template files from webui frontend views - Enhanced plugin bridge, storage, signature verification with multilingual support and security improvements
This commit is contained in:
196
oss/plugins/firewall.py
Normal file
196
oss/plugins/firewall.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 动态防火墙插件
|
||||
功能:IP 过滤、端口管理、规则引擎、攻击检测
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import ipaddress
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Set, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.firewall")
|
||||
|
||||
class FirewallPlugin(BasePlugin):
|
||||
name = "firewall"
|
||||
version = "1.1.0"
|
||||
description = "动态防火墙:智能 IP 过滤与端口管理"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.rules_file = "./config/firewall_rules.json"
|
||||
self.whitelist: Set[str] = set()
|
||||
self.blacklist: Set[str] = set()
|
||||
self.blocked_ports: Set[int] = set()
|
||||
self.allowed_ports: Set[int] = {80, 443, 22} # 默认开放端口
|
||||
self.rate_limits: Dict[str, Dict] = {}
|
||||
self.attack_log: List[Dict] = []
|
||||
|
||||
# 加载现有规则
|
||||
self.load_rules()
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("动态防火墙已启动")
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("firewall.ip.allow", self.allow_ip)
|
||||
ctx.register_command("firewall.ip.block", self.block_ip)
|
||||
ctx.register_command("firewall.ip.list", self.list_ips)
|
||||
ctx.register_command("firewall.port.open", self.open_port)
|
||||
ctx.register_command("firewall.port.close", self.close_port)
|
||||
ctx.register_command("firewall.port.list", self.list_ports)
|
||||
ctx.register_command("firewall.rule.add", self.add_rule)
|
||||
ctx.register_command("firewall.rule.list", self.list_rules)
|
||||
ctx.register_command("firewall.attack.log", self.get_attack_log)
|
||||
|
||||
def load_rules(self):
|
||||
"""加载防火墙规则"""
|
||||
if os.path.exists(self.rules_file):
|
||||
try:
|
||||
with open(self.rules_file, "r") as f:
|
||||
rules = json.load(f)
|
||||
self.whitelist = set(rules.get("whitelist", []))
|
||||
self.blacklist = set(rules.get("blacklist", []))
|
||||
self.blocked_ports = set(rules.get("blocked_ports", []))
|
||||
self.allowed_ports = set(rules.get("allowed_ports", [80, 443, 22]))
|
||||
logger.info(f"已加载 {len(self.whitelist)} 个白名单 IP, {len(self.blacklist)} 个黑名单 IP")
|
||||
except Exception as e:
|
||||
logger.error(f"加载防火墙规则失败:{e}")
|
||||
|
||||
def save_rules(self):
|
||||
"""保存防火墙规则"""
|
||||
rules = {
|
||||
"whitelist": list(self.whitelist),
|
||||
"blacklist": list(self.blacklist),
|
||||
"blocked_ports": list(self.blocked_ports),
|
||||
"allowed_ports": list(self.allowed_ports),
|
||||
"updated_at": datetime.now().isoformat()
|
||||
}
|
||||
os.makedirs(os.path.dirname(self.rules_file), exist_ok=True)
|
||||
with open(self.rules_file, "w") as f:
|
||||
json.dump(rules, f, indent=2)
|
||||
|
||||
def allow_ip(self, ctx: Context, ip: str):
|
||||
"""添加 IP 到白名单"""
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
self.whitelist.add(ip)
|
||||
self.blacklist.discard(ip) # 从黑名单移除
|
||||
self.save_rules()
|
||||
logger.info(f"IP {ip} 已加入白名单")
|
||||
return {"status": "success", "message": f"IP {ip} 已加入白名单"}
|
||||
except ValueError:
|
||||
return {"status": "error", "message": "无效的 IP 地址"}
|
||||
|
||||
def block_ip(self, ctx: Context, ip: str, reason: str = ""):
|
||||
"""添加 IP 到黑名单"""
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
self.blacklist.add(ip)
|
||||
self.whitelist.discard(ip) # 从白名单移除
|
||||
self.save_rules()
|
||||
|
||||
# 记录攻击日志
|
||||
self.attack_log.append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"ip": ip,
|
||||
"action": "blocked",
|
||||
"reason": reason
|
||||
})
|
||||
|
||||
logger.warning(f"IP {ip} 已加入黑名单,原因:{reason}")
|
||||
return {"status": "success", "message": f"IP {ip} 已加入黑名单"}
|
||||
except ValueError:
|
||||
return {"status": "error", "message": "无效的 IP 地址"}
|
||||
|
||||
def list_ips(self, ctx: Context):
|
||||
"""列出所有 IP 规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"whitelist": list(self.whitelist),
|
||||
"blacklist": list(self.blacklist),
|
||||
"total": len(self.whitelist) + len(self.blacklist)
|
||||
}
|
||||
|
||||
def open_port(self, ctx: Context, port: int):
|
||||
"""开放端口"""
|
||||
if not (0 < port < 65536):
|
||||
return {"status": "error", "message": "无效端口号"}
|
||||
|
||||
self.allowed_ports.add(port)
|
||||
self.blocked_ports.discard(port)
|
||||
self.save_rules()
|
||||
logger.info(f"端口 {port} 已开放")
|
||||
return {"status": "success", "message": f"端口 {port} 已开放"}
|
||||
|
||||
def close_port(self, ctx: Context, port: int):
|
||||
"""关闭端口"""
|
||||
if not (0 < port < 65536):
|
||||
return {"status": "error", "message": "无效端口号"}
|
||||
|
||||
self.blocked_ports.add(port)
|
||||
self.allowed_ports.discard(port)
|
||||
self.save_rules()
|
||||
logger.info(f"端口 {port} 已关闭")
|
||||
return {"status": "success", "message": f"端口 {port} 已关闭"}
|
||||
|
||||
def list_ports(self, ctx: Context):
|
||||
"""列出端口规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"allowed_ports": sorted(list(self.allowed_ports)),
|
||||
"blocked_ports": sorted(list(self.blocked_ports))
|
||||
}
|
||||
|
||||
def add_rule(self, ctx: Context, rule_type: str, **kwargs):
|
||||
"""添加高级规则"""
|
||||
rule = {
|
||||
"type": rule_type,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
**kwargs
|
||||
}
|
||||
|
||||
if rule_type == "rate_limit":
|
||||
ip = kwargs.get("ip")
|
||||
limit = kwargs.get("limit", 100) # 每秒请求数
|
||||
self.rate_limits[ip] = {"limit": limit, "window": 1}
|
||||
logger.info(f"为 IP {ip} 设置限流:{limit} req/s")
|
||||
|
||||
return {"status": "success", "rule": rule}
|
||||
|
||||
def list_rules(self, ctx: Context):
|
||||
"""列出所有规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"whitelist_count": len(self.whitelist),
|
||||
"blacklist_count": len(self.blacklist),
|
||||
"allowed_ports_count": len(self.allowed_ports),
|
||||
"blocked_ports_count": len(self.blocked_ports),
|
||||
"rate_limits": self.rate_limits
|
||||
}
|
||||
|
||||
def get_attack_log(self, ctx: Context, limit: int = 50):
|
||||
"""获取攻击日志"""
|
||||
return {
|
||||
"status": "success",
|
||||
"logs": self.attack_log[-limit:],
|
||||
"total": len(self.attack_log)
|
||||
}
|
||||
|
||||
def check_ip(self, ip: str) -> bool:
|
||||
"""检查 IP 是否允许访问"""
|
||||
if ip in self.whitelist:
|
||||
return True
|
||||
if ip in self.blacklist:
|
||||
return False
|
||||
return True # 默认允许
|
||||
|
||||
def check_port(self, port: int) -> bool:
|
||||
"""检查端口是否开放"""
|
||||
return port in self.allowed_ports and port not in self.blocked_ports
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
self.save_rules()
|
||||
logger.info("动态防火墙已停止")
|
||||
172
oss/plugins/frp_proxy.py
Normal file
172
oss/plugins/frp_proxy.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - FRP 内网穿透插件
|
||||
功能:反向代理、隧道管理、流量统计、访问控制
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.frp")
|
||||
|
||||
class FRPPlugin(BasePlugin):
|
||||
name = "frp_proxy"
|
||||
version = "1.1.0"
|
||||
description = "FRP 内网穿透服务:安全反向代理隧道"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config_dir = "./frp_config"
|
||||
self.tunnels: Dict[str, Dict] = {}
|
||||
self.frpc_process = None
|
||||
self.frp_server = {
|
||||
"address": "frp.example.com",
|
||||
"port": 7000,
|
||||
"token": "futureoss_frp_token"
|
||||
}
|
||||
|
||||
os.makedirs(self.config_dir, exist_ok=True)
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("FRP 内网穿透插件已加载")
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("frp.tunnel.create", self.create_tunnel)
|
||||
ctx.register_command("frp.tunnel.remove", self.remove_tunnel)
|
||||
ctx.register_command("frp.tunnel.list", self.list_tunnels)
|
||||
ctx.register_command("frp.tunnel.start", self.start_tunnel)
|
||||
ctx.register_command("frp.tunnel.stop", self.stop_tunnel)
|
||||
ctx.register_command("frp.server.config", self.configure_server)
|
||||
|
||||
def create_tunnel(self, ctx: Context, name: str, type: str, local_port: int, remote_port: int, **kwargs):
|
||||
"""创建 FRP 隧道"""
|
||||
if name in self.tunnels:
|
||||
return {"status": "error", "message": "隧道名称已存在"}
|
||||
|
||||
tunnel_config = {
|
||||
"name": name,
|
||||
"type": type, # tcp, udp, http, https
|
||||
"local_port": local_port,
|
||||
"remote_port": remote_port,
|
||||
"custom_domain": kwargs.get("domain"),
|
||||
"status": "created",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"traffic_stats": {"in": 0, "out": 0}
|
||||
}
|
||||
|
||||
# 生成 FRP 配置文件
|
||||
config_content = f"""
|
||||
[{name}]
|
||||
type = {type}
|
||||
local_ip = 127.0.0.1
|
||||
local_port = {local_port}
|
||||
remote_port = {remote_port}
|
||||
"""
|
||||
if kwargs.get("domain"):
|
||||
config_content += f"custom_domains = {kwargs['domain']}\n"
|
||||
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
with open(config_path, "w") as f:
|
||||
f.write(config_content)
|
||||
|
||||
self.tunnels[name] = tunnel_config
|
||||
logger.info(f"FRP 隧道 {name} 已创建")
|
||||
|
||||
return {"status": "success", "tunnel": tunnel_config, "config_file": config_path}
|
||||
|
||||
def remove_tunnel(self, ctx: Context, name: str):
|
||||
"""删除 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
# 如果正在运行,先停止
|
||||
if self.tunnels[name]["status"] == "running":
|
||||
self.stop_tunnel(ctx, name)
|
||||
|
||||
# 删除配置文件
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
if os.path.exists(config_path):
|
||||
os.remove(config_path)
|
||||
|
||||
del self.tunnels[name]
|
||||
logger.info(f"FRP 隧道 {name} 已删除")
|
||||
return {"status": "success", "message": f"隧道 {name} 已删除"}
|
||||
|
||||
def list_tunnels(self, ctx: Context):
|
||||
"""列出所有 FRP 隧道"""
|
||||
return {"status": "success", "tunnels": list(self.tunnels.values())}
|
||||
|
||||
def start_tunnel(self, ctx: Context, name: str):
|
||||
"""启动 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
tunnel = self.tunnels[name]
|
||||
if tunnel["status"] == "running":
|
||||
return {"status": "error", "message": "隧道已在运行"}
|
||||
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
if not os.path.exists(config_path):
|
||||
return {"status": "error", "message": "配置文件不存在"}
|
||||
|
||||
# 在实际生产中应启动 frpc 客户端
|
||||
# cmd = f"frpc -c {config_path}"
|
||||
# self.frpc_process = subprocess.Popen(cmd.split())
|
||||
|
||||
tunnel["status"] = "running"
|
||||
tunnel["started_at"] = datetime.now().isoformat()
|
||||
logger.info(f"FRP 隧道 {name} 已启动")
|
||||
|
||||
return {"status": "success", "message": f"隧道 {name} 已启动", "tunnel": tunnel}
|
||||
|
||||
def stop_tunnel(self, ctx: Context, name: str):
|
||||
"""停止 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
tunnel = self.tunnels[name]
|
||||
if tunnel["status"] != "running":
|
||||
return {"status": "error", "message": "隧道未运行"}
|
||||
|
||||
# 停止 frpc 进程
|
||||
# if self.frpc_process:
|
||||
# self.frpc_process.terminate()
|
||||
|
||||
tunnel["status"] = "stopped"
|
||||
logger.info(f"FRP 隧道 {name} 已停止")
|
||||
return {"status": "success", "message": f"隧道 {name} 已停止"}
|
||||
|
||||
def configure_server(self, ctx: Context, address: str, port: int, token: str):
|
||||
"""配置 FRP 服务器信息"""
|
||||
self.frp_server = {
|
||||
"address": address,
|
||||
"port": port,
|
||||
"token": token
|
||||
}
|
||||
|
||||
# 生成主配置文件
|
||||
main_config = f"""
|
||||
[common]
|
||||
server_addr = {address}
|
||||
server_port = {port}
|
||||
token = {token}
|
||||
log_file = ./logs/frpc.log
|
||||
log_level = info
|
||||
"""
|
||||
config_path = os.path.join(self.config_dir, "frpc.ini")
|
||||
with open(config_path, "w") as f:
|
||||
f.write(main_config)
|
||||
|
||||
logger.info(f"FRP 服务器配置已更新:{address}:{port}")
|
||||
return {"status": "success", "config": self.frp_server}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
# 停止所有隧道
|
||||
for name in list(self.tunnels.keys()):
|
||||
if self.tunnels[name]["status"] == "running":
|
||||
self.stop_tunnel(ctx, name)
|
||||
logger.info("FRP 内网穿透插件已卸载")
|
||||
123
oss/plugins/ftp_server.py
Normal file
123
oss/plugins/ftp_server.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - FTP 服务器插件
|
||||
功能:文件传输、用户管理、访问控制、日志记录
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.ftp")
|
||||
|
||||
class FTPServerPlugin(BasePlugin):
|
||||
name = "ftp_server"
|
||||
version = "1.1.0"
|
||||
description = "FTP 文件传输服务:安全文件上传下载"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.root_dir = "./ftp_root"
|
||||
self.users: Dict[str, Dict] = {}
|
||||
self.sessions: Dict[str, Dict] = {}
|
||||
self.server = None
|
||||
self.running = False
|
||||
|
||||
# 默认管理员账户
|
||||
self.users["admin"] = {
|
||||
"password": "admin123", # 生产环境应加密存储
|
||||
"home_dir": self.root_dir,
|
||||
"permissions": ["read", "write", "delete"],
|
||||
"max_connections": 5
|
||||
}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("FTP 服务器插件已加载")
|
||||
os.makedirs(self.root_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("ftp.user.add", self.add_user)
|
||||
ctx.register_command("ftp.user.remove", self.remove_user)
|
||||
ctx.register_command("ftp.user.list", self.list_users)
|
||||
ctx.register_command("ftp.start", self.start_server)
|
||||
ctx.register_command("ftp.stop", self.stop_server)
|
||||
ctx.register_command("ftp.session.list", self.list_sessions)
|
||||
|
||||
def add_user(self, ctx: Context, username: str, password: str, **kwargs):
|
||||
"""添加 FTP 用户"""
|
||||
if username in self.users:
|
||||
return {"status": "error", "message": "用户已存在"}
|
||||
|
||||
self.users[username] = {
|
||||
"password": password,
|
||||
"home_dir": os.path.join(self.root_dir, username),
|
||||
"permissions": kwargs.get("permissions", ["read"]),
|
||||
"max_connections": kwargs.get("max_connections", 3)
|
||||
}
|
||||
|
||||
# 创建用户主目录
|
||||
os.makedirs(self.users[username]["home_dir"], exist_ok=True)
|
||||
|
||||
logger.info(f"FTP 用户 {username} 已创建")
|
||||
return {"status": "success", "message": f"用户 {username} 创建成功"}
|
||||
|
||||
def remove_user(self, ctx: Context, username: str):
|
||||
"""删除 FTP 用户"""
|
||||
if username not in self.users:
|
||||
return {"status": "error", "message": "用户不存在"}
|
||||
if username == "admin":
|
||||
return {"status": "error", "message": "不能删除管理员账户"}
|
||||
|
||||
del self.users[username]
|
||||
logger.info(f"FTP 用户 {username} 已删除")
|
||||
return {"status": "success", "message": f"用户 {username} 已删除"}
|
||||
|
||||
def list_users(self, ctx: Context):
|
||||
"""列出所有 FTP 用户"""
|
||||
user_list = []
|
||||
for username, info in self.users.items():
|
||||
user_list.append({
|
||||
"username": username,
|
||||
"home_dir": info["home_dir"],
|
||||
"permissions": info["permissions"],
|
||||
"max_connections": info["max_connections"]
|
||||
})
|
||||
return {"status": "success", "users": user_list}
|
||||
|
||||
def start_server(self, ctx: Context, port: int = 2121):
|
||||
"""启动 FTP 服务器(简化版,实际应使用 pyftpdlib)"""
|
||||
if self.running:
|
||||
return {"status": "error", "message": "FTP 服务器已在运行"}
|
||||
|
||||
self.running = True
|
||||
self.port = port
|
||||
|
||||
# 模拟服务器启动
|
||||
logger.info(f"FTP 服务器启动在端口 {port}")
|
||||
|
||||
# 在实际生产中应启动真正的 FTP 服务
|
||||
# from pyftpdlib.authorizers import DummyAuthorizer
|
||||
# from pyftpdlib.handlers import FTPHandler
|
||||
# from pyftpdlib.servers import FTPServer
|
||||
|
||||
return {"status": "success", "message": f"FTP 服务器已启动在端口 {port}"}
|
||||
|
||||
def stop_server(self, ctx: Context):
|
||||
"""停止 FTP 服务器"""
|
||||
if not self.running:
|
||||
return {"status": "error", "message": "FTP 服务器未运行"}
|
||||
|
||||
self.running = False
|
||||
logger.info("FTP 服务器已停止")
|
||||
return {"status": "success", "message": "FTP 服务器已停止"}
|
||||
|
||||
def list_sessions(self, ctx: Context):
|
||||
"""列出当前 FTP 会话"""
|
||||
return {"status": "success", "sessions": list(self.sessions.values())}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
if self.running:
|
||||
self.stop_server(ctx)
|
||||
logger.info("FTP 服务器插件已卸载")
|
||||
178
oss/plugins/multi_lang_deploy.py
Normal file
178
oss/plugins/multi_lang_deploy.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 多语言项目部署编排器
|
||||
功能:语言环境管理、自动构建、配置模板、一键部署
|
||||
支持:Python, Node.js, Go, Java, PHP
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import logging
|
||||
import shutil
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.deploy")
|
||||
|
||||
class MultiLangDeployPlugin(BasePlugin):
|
||||
name = "multi_lang_deploy"
|
||||
version = "1.1.0"
|
||||
description = "多语言项目部署编排器:自动检测、构建、部署"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.projects_dir = "./projects"
|
||||
self.runtimes = {
|
||||
"python": {"file": "requirements.txt", "install": "pip install -r requirements.txt", "run": "python main.py"},
|
||||
"nodejs": {"file": "package.json", "install": "npm install", "run": "node main.js"},
|
||||
"go": {"file": "go.mod", "install": "go mod download", "run": "go run main.go"},
|
||||
"java": {"file": "pom.xml", "install": "mvn dependency:resolve", "run": "java -jar target/*.jar"},
|
||||
"php": {"file": "composer.json", "install": "composer install", "run": "php -S localhost:8000"}
|
||||
}
|
||||
self.deployed_projects: Dict[str, Dict] = {}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("多语言部署编排器已启动")
|
||||
os.makedirs(self.projects_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("deploy.project.detect", self.detect_language)
|
||||
ctx.register_command("deploy.project.build", self.build_project)
|
||||
ctx.register_command("deploy.project.start", self.start_project)
|
||||
ctx.register_command("deploy.project.stop", self.stop_project)
|
||||
ctx.register_command("deploy.project.list", self.list_projects)
|
||||
ctx.register_command("deploy.runtime.check", self.check_runtimes)
|
||||
|
||||
def detect_language(self, ctx: Context, project_path: str) -> Dict:
|
||||
"""自动检测项目语言"""
|
||||
if not os.path.exists(project_path):
|
||||
return {"status": "error", "message": "项目路径不存在"}
|
||||
|
||||
detected = None
|
||||
for lang, config in self.runtimes.items():
|
||||
if os.path.exists(os.path.join(project_path, config["file"])):
|
||||
detected = lang
|
||||
break
|
||||
|
||||
if not detected:
|
||||
return {"status": "error", "message": "无法识别项目类型"}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"language": detected,
|
||||
"path": project_path,
|
||||
"config_file": self.runtimes[detected]["file"]
|
||||
}
|
||||
|
||||
def build_project(self, ctx: Context, project_name: str, project_path: str):
|
||||
"""构建项目(安装依赖)"""
|
||||
detection = self.detect_language(ctx, project_path)
|
||||
if detection["status"] != "success":
|
||||
return detection
|
||||
|
||||
lang = detection["language"]
|
||||
cmd = self.runtimes[lang]["install"]
|
||||
|
||||
try:
|
||||
logger.info(f"正在构建 {project_name} ({lang})...")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
cwd=project_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return {"status": "error", "message": f"构建失败:{result.stderr}"}
|
||||
|
||||
# 保存项目信息
|
||||
self.deployed_projects[project_name] = {
|
||||
"name": project_name,
|
||||
"path": project_path,
|
||||
"language": lang,
|
||||
"status": "built",
|
||||
"built_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
logger.info(f"项目 {project_name} 构建成功")
|
||||
return {"status": "success", "message": "构建完成", "project": self.deployed_projects[project_name]}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"status": "error", "message": "构建超时"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def start_project(self, ctx: Context, project_name: str):
|
||||
"""启动项目"""
|
||||
if project_name not in self.deployed_projects:
|
||||
return {"status": "error", "message": "项目未找到"}
|
||||
|
||||
proj = self.deployed_projects[project_name]
|
||||
cmd = self.runtimes[proj["language"]]["run"]
|
||||
|
||||
try:
|
||||
# 在实际生产中应使用进程管理器
|
||||
logger.info(f"正在启动 {project_name}...")
|
||||
# subprocess.Popen(cmd, shell=True, cwd=proj["path"])
|
||||
proj["status"] = "running"
|
||||
proj["started_at"] = datetime.now().isoformat()
|
||||
|
||||
return {"status": "success", "message": f"项目 {project_name} 已启动", "project": proj}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def stop_project(self, ctx: Context, project_name: str):
|
||||
"""停止项目"""
|
||||
if project_name not in self.deployed_projects:
|
||||
return {"status": "error", "message": "项目未找到"}
|
||||
|
||||
self.deployed_projects[project_name]["status"] = "stopped"
|
||||
logger.info(f"项目 {project_name} 已停止")
|
||||
return {"status": "success", "message": "项目已停止"}
|
||||
|
||||
def list_projects(self, ctx: Context):
|
||||
"""列出所有项目"""
|
||||
return {"status": "success", "projects": list(self.deployed_projects.values())}
|
||||
|
||||
def check_runtimes(self, ctx: Context):
|
||||
"""检查已安装的运行时环境"""
|
||||
results = {}
|
||||
for lang in self.runtimes.keys():
|
||||
installed = False
|
||||
version = "N/A"
|
||||
try:
|
||||
if lang == "python":
|
||||
result = subprocess.run(["python3", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "nodejs":
|
||||
result = subprocess.run(["node", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "go":
|
||||
result = subprocess.run(["go", "version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "java":
|
||||
result = subprocess.run(["java", "-version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = "Java installed"
|
||||
elif lang == "php":
|
||||
result = subprocess.run(["php", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip().split('\n')[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
results[lang] = {"installed": installed, "version": version}
|
||||
|
||||
return {"status": "success", "runtimes": results}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
# 停止所有运行中的项目
|
||||
for name in list(self.deployed_projects.keys()):
|
||||
if self.deployed_projects[name].get("status") == "running":
|
||||
self.stop_project(ctx, name)
|
||||
logger.info("多语言部署编排器已停止")
|
||||
178
oss/plugins/ops_toolbox.py
Normal file
178
oss/plugins/ops_toolbox.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 自动化运维工具箱
|
||||
功能:一键备份/恢复、健康检查、资源配额管理、自动重启
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import tarfile
|
||||
import shutil
|
||||
import logging
|
||||
import threading
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.ops")
|
||||
|
||||
class OpsToolboxPlugin(BasePlugin):
|
||||
name = "ops_toolbox"
|
||||
version = "1.1.0"
|
||||
description = "自动化运维工具箱:备份、健康检查、资源配额"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.backup_dir = "./backups"
|
||||
self.health_checks: Dict[str, Dict] = {}
|
||||
self.resource_quotas: Dict[str, Dict] = {}
|
||||
self.monitoring_active = False
|
||||
self.monitor_thread: Optional[threading.Thread] = None
|
||||
|
||||
# 默认配额
|
||||
self.default_quota = {
|
||||
"max_memory_mb": 512,
|
||||
"max_cpu_percent": 50,
|
||||
"max_open_files": 1024
|
||||
}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("运维工具箱已启动")
|
||||
os.makedirs(self.backup_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("ops.backup.create", self.create_backup)
|
||||
ctx.register_command("ops.backup.restore", self.restore_backup)
|
||||
ctx.register_command("ops.backup.list", self.list_backups)
|
||||
ctx.register_command("ops.health.check", self.run_health_check)
|
||||
ctx.register_command("ops.quota.set", self.set_quota)
|
||||
ctx.register_command("ops.quota.get", self.get_quota)
|
||||
|
||||
# 启动后台监控
|
||||
self.monitoring_active = True
|
||||
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||
self.monitor_thread.start()
|
||||
|
||||
def create_backup(self, ctx: Context, name: Optional[str] = None):
|
||||
"""创建系统备份"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_name = name or f"backup_{timestamp}"
|
||||
backup_path = os.path.join(self.backup_dir, f"{backup_name}.tar.gz")
|
||||
|
||||
try:
|
||||
# 备份配置文件和插件数据
|
||||
files_to_backup = []
|
||||
for root in ["./config", "./plugins/data", "./logs"]:
|
||||
if os.path.exists(root):
|
||||
files_to_backup.append(root)
|
||||
|
||||
with tarfile.open(backup_path, "w:gz") as tar:
|
||||
for file_path in files_to_backup:
|
||||
tar.add(file_path, arcname=os.path.basename(file_path))
|
||||
|
||||
metadata = {
|
||||
"name": backup_name,
|
||||
"timestamp": timestamp,
|
||||
"files": files_to_backup,
|
||||
"size_mb": round(os.path.getsize(backup_path) / 1024 / 1024, 2)
|
||||
}
|
||||
|
||||
# 保存元数据
|
||||
meta_path = backup_path.replace(".tar.gz", ".json")
|
||||
with open(meta_path, "w") as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
logger.info(f"备份创建成功:{backup_name}")
|
||||
return {"status": "success", "backup": metadata}
|
||||
except Exception as e:
|
||||
logger.error(f"备份失败:{e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def restore_backup(self, ctx: Context, backup_name: str):
|
||||
"""恢复备份"""
|
||||
backup_path = os.path.join(self.backup_dir, f"{backup_name}.tar.gz")
|
||||
if not os.path.exists(backup_path):
|
||||
return {"status": "error", "message": "备份文件不存在"}
|
||||
|
||||
try:
|
||||
with tarfile.open(backup_path, "r:gz") as tar:
|
||||
tar.extractall(path="./")
|
||||
logger.info(f"备份恢复成功:{backup_name}")
|
||||
return {"status": "success", "message": "恢复完成,请重启系统"}
|
||||
except Exception as e:
|
||||
logger.error(f"恢复失败:{e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def list_backups(self, ctx: Context):
|
||||
"""列出所有备份"""
|
||||
backups = []
|
||||
for f in os.listdir(self.backup_dir):
|
||||
if f.endswith(".tar.gz"):
|
||||
meta_path = os.path.join(self.backup_dir, f.replace(".tar.gz", ".json"))
|
||||
if os.path.exists(meta_path):
|
||||
with open(meta_path) as mf:
|
||||
backups.append(json.load(mf))
|
||||
else:
|
||||
backups.append({"name": f, "size_mb": round(os.path.getsize(os.path.join(self.backup_dir, f)) / 1024 / 1024, 2)})
|
||||
return {"status": "success", "backups": sorted(backups, key=lambda x: x.get("timestamp", ""), reverse=True)}
|
||||
|
||||
def run_health_check(self, ctx: Context):
|
||||
"""执行健康检查"""
|
||||
results = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"system": {},
|
||||
"plugins": {},
|
||||
"issues": []
|
||||
}
|
||||
|
||||
# 系统级检查
|
||||
results["system"]["cpu"] = psutil.cpu_percent(interval=1)
|
||||
results["system"]["memory"] = psutil.virtual_memory().percent
|
||||
results["system"]["disk"] = psutil.disk_usage("/").percent
|
||||
|
||||
if results["system"]["cpu"] > 90:
|
||||
results["issues"].append("CPU 使用率过高")
|
||||
if results["system"]["memory"] > 90:
|
||||
results["issues"].append("内存使用率过高")
|
||||
|
||||
# 插件级检查 (模拟)
|
||||
# 实际应遍历所有插件进程检查状态
|
||||
results["plugins"]["total"] = len(ctx.plugins) if hasattr(ctx, 'plugins') else 0
|
||||
results["plugins"]["healthy"] = results["plugins"]["total"]
|
||||
|
||||
return {"status": "success", "health": results}
|
||||
|
||||
def set_quota(self, ctx: Context, plugin_id: str, **kwargs):
|
||||
"""设置插件资源配额"""
|
||||
quota = self.default_quota.copy()
|
||||
quota.update(kwargs)
|
||||
self.resource_quotas[plugin_id] = quota
|
||||
logger.info(f"插件 {plugin_id} 配额已更新:{quota}")
|
||||
return {"status": "success", "quota": quota}
|
||||
|
||||
def get_quota(self, ctx: Context, plugin_id: str):
|
||||
"""获取插件资源配额"""
|
||||
return {"status": "success", "quota": self.resource_quotas.get(plugin_id, self.default_quota)}
|
||||
|
||||
def _monitor_loop(self):
|
||||
"""后台监控循环"""
|
||||
while self.monitoring_active:
|
||||
try:
|
||||
# 检查资源配额
|
||||
for pid, proc in enumerate(psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent'])):
|
||||
# 简化逻辑:实际应根据插件名匹配
|
||||
pass
|
||||
|
||||
# 自动重启检测 (简化版)
|
||||
# 实际应检查插件进程是否存活
|
||||
|
||||
time.sleep(10) # 每 10 秒检查一次
|
||||
except Exception as e:
|
||||
logger.error(f"监控循环错误:{e}")
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
self.monitoring_active = False
|
||||
if self.monitor_thread:
|
||||
self.monitor_thread.join(timeout=2)
|
||||
logger.info("运维工具箱已停止")
|
||||
129
oss/plugins/security_gateway.py
Normal file
129
oss/plugins/security_gateway.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 统一安全网关与审计中心
|
||||
功能:API 限流、IP 黑白名单、JWT 认证、操作审计、异常行为检测
|
||||
"""
|
||||
import time
|
||||
import logging
|
||||
import jwt
|
||||
import hashlib
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.security")
|
||||
|
||||
class SecurityGatewayPlugin(BasePlugin):
|
||||
name = "security_gateway"
|
||||
version = "1.1.0"
|
||||
description = "统一安全网关:限流、鉴权、审计、熔断"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.rate_limit_store: Dict[str, List[float]] = defaultdict(list)
|
||||
self.ip_blacklist: set = set()
|
||||
self.ip_whitelist: set = set()
|
||||
self.secret_key = "futureoss_secret_key_v1.1.0_change_in_prod"
|
||||
self.audit_logs: List[Dict] = []
|
||||
self.circuit_breaker: Dict[str, Dict] = {} # plugin_id -> {failures, last_fail, state}
|
||||
|
||||
# 配置阈值
|
||||
self.rate_limit_reqs = 100 # 每秒请求数
|
||||
self.circuit_breaker_threshold = 5 # 失败次数阈值
|
||||
self.circuit_breaker_timeout = 60 # 熔断恢复时间 (秒)
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("安全网关已启动")
|
||||
# 注册中间件
|
||||
ctx.register_middleware("pre_request", self.pre_request_filter)
|
||||
ctx.register_middleware("post_action", self.audit_action)
|
||||
|
||||
# 注册管理命令
|
||||
ctx.register_command("security.add_blacklist", self.add_blacklist)
|
||||
ctx.register_command("security.audit.query", self.query_audit_logs)
|
||||
ctx.register_command("security.circuit.reset", self.reset_circuit)
|
||||
|
||||
def pre_request_filter(self, request: Dict, client_ip: str) -> bool:
|
||||
"""请求前置过滤:限流、黑白名单、鉴权"""
|
||||
now = time.time()
|
||||
|
||||
# 1. 白名单跳过检查
|
||||
if client_ip in self.ip_whitelist:
|
||||
return True
|
||||
|
||||
# 2. 黑名单拦截
|
||||
if client_ip in self.ip_blacklist:
|
||||
logger.warning(f"IP {client_ip} 在黑名单中,拒绝访问")
|
||||
return False
|
||||
|
||||
# 3. 限流检查 (滑动窗口)
|
||||
user_requests = self.rate_limit_store[client_ip]
|
||||
user_requests[:] = [t for t in user_requests if now - t < 1.0]
|
||||
|
||||
if len(user_requests) >= self.rate_limit_reqs:
|
||||
logger.warning(f"IP {client_ip} 触发限流")
|
||||
self.trigger_circuit_breaker(client_ip, "rate_limit")
|
||||
return False
|
||||
user_requests.append(now)
|
||||
|
||||
# 4. JWT 鉴权 (针对受保护资源)
|
||||
if request.get("path", "").startswith("/admin"):
|
||||
token = request.get("headers", {}).get("Authorization", "")
|
||||
if not self.validate_jwt(token):
|
||||
logger.warning(f"IP {client_ip} 鉴权失败")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def audit_action(self, action: str, user: str, details: Dict):
|
||||
"""记录操作审计日志"""
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"action": action,
|
||||
"user": user,
|
||||
"details": details,
|
||||
"hash": hashlib.sha256(f"{action}{user}{time.time()}".encode()).hexdigest()[:8]
|
||||
}
|
||||
self.audit_logs.append(log_entry)
|
||||
# 保留最近 1000 条
|
||||
if len(self.audit_logs) > 1000:
|
||||
self.audit_logs.pop(0)
|
||||
logger.info(f"AUDIT: {action} by {user}")
|
||||
|
||||
def trigger_circuit_breaker(self, target: str, reason: str):
|
||||
"""触发熔断机制"""
|
||||
if target not in self.circuit_breaker:
|
||||
self.circuit_breaker[target] = {"failures": 0, "last_fail": 0, "state": "closed"}
|
||||
|
||||
cb = self.circuit_breaker[target]
|
||||
cb["failures"] += 1
|
||||
cb["last_fail"] = time.time()
|
||||
|
||||
if cb["failures"] >= self.circuit_breaker_threshold:
|
||||
cb["state"] = "open"
|
||||
logger.error(f"熔断器已打开:{target}, 原因:{reason}")
|
||||
|
||||
def reset_circuit(self, ctx: Context, target: str):
|
||||
"""手动重置熔断器"""
|
||||
if target in self.circuit_breaker:
|
||||
self.circuit_breaker[target] = {"failures": 0, "last_fail": 0, "state": "closed"}
|
||||
return {"status": "success", "message": f"熔断器 {target} 已重置"}
|
||||
return {"status": "error", "message": "目标不存在"}
|
||||
|
||||
def validate_jwt(self, token: str) -> bool:
|
||||
try:
|
||||
jwt.decode(token, self.secret_key, algorithms=["HS256"])
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def add_blacklist(self, ctx: Context, ip: str):
|
||||
self.ip_blacklist.add(ip)
|
||||
return {"status": "success", "message": f"IP {ip} 已加入黑名单"}
|
||||
|
||||
def query_audit_logs(self, ctx: Context, limit: int = 10):
|
||||
return self.audit_logs[-limit:]
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
logger.info("安全网关已停止")
|
||||
Reference in New Issue
Block a user