mirror of https://github.com/Aidaho12/haproxy-wi
509 lines
14 KiB
Python
509 lines
14 KiB
Python
import re
|
|
from annotated_types import Gt, Le
|
|
from typing import Optional, Annotated, Union, Literal, Any, Dict, List
|
|
|
|
from shlex import quote
|
|
from pydantic_core import CoreSchema, core_schema
|
|
from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler, AnyUrl, root_validator, EmailStr
|
|
|
|
DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
|
|
WildcardDomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9\*](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")]
|
|
|
|
|
|
class EscapedString(str):
|
|
pattern = re.compile('[&;|$`]')
|
|
|
|
@classmethod
|
|
def validate(cls, field_value, info) -> str:
|
|
if isinstance(field_value, str):
|
|
if cls.pattern.search(field_value):
|
|
return re.sub(cls.pattern, '', field_value)
|
|
elif '..' in field_value:
|
|
raise ValueError('nice try')
|
|
elif field_value == '':
|
|
return field_value
|
|
else:
|
|
return quote(field_value.rstrip())
|
|
|
|
@classmethod
|
|
def __get_pydantic_core_schema__(
|
|
cls, source_type: Any, handler: GetCoreSchemaHandler
|
|
) -> CoreSchema:
|
|
|
|
return core_schema.chain_schema(
|
|
[
|
|
core_schema.with_info_plain_validator_function(
|
|
function=cls.validate,
|
|
)
|
|
]
|
|
)
|
|
|
|
|
|
class BaseResponse(BaseModel):
|
|
status: str = 'Ok'
|
|
|
|
|
|
class IdResponse(BaseResponse):
|
|
id: int
|
|
|
|
|
|
class IdStrResponse(BaseResponse):
|
|
id: str
|
|
|
|
|
|
class IdDataResponse(IdResponse):
|
|
data: str
|
|
|
|
|
|
class IdDataStrResponse(IdStrResponse):
|
|
data: str
|
|
|
|
|
|
class DataResponse(BaseModel):
|
|
data: Union[list, dict]
|
|
|
|
|
|
class DataStrResponse(BaseModel):
|
|
data: str
|
|
|
|
|
|
class ErrorResponse(BaseModel):
|
|
status: str = 'failed'
|
|
error: Union[str, list]
|
|
|
|
|
|
class UdpBackendConfig(BaseModel):
|
|
backend_ip: Union[IPvAnyAddress, DomainName]
|
|
port: Annotated[int, Gt(1), Le(65535)]
|
|
weight: Annotated[int, Gt(0), Le(51)]
|
|
|
|
|
|
class UdpListenerRequest(BaseModel):
|
|
name: EscapedString
|
|
cluster_id: Optional[int] = None
|
|
server_id: Optional[int] = None
|
|
vip: Optional[str] = None
|
|
port: Annotated[int, Gt(1), Le(65535)]
|
|
group_id: int
|
|
config: List[UdpBackendConfig]
|
|
description: Optional[EscapedString] = None
|
|
lb_algo: Literal['rr', 'wrr', 'lc', 'wlc', 'sh', 'dh', 'wlc', 'lblc']
|
|
check_enabled: Optional[bool] = 1
|
|
reconfigure: Optional[bool] = 0
|
|
delay_loop: Optional[int] = 10
|
|
delay_before_retry: Optional[int] = 10
|
|
retry: Optional[int] = 3
|
|
|
|
|
|
class UserPost(BaseModel):
|
|
username: EscapedString
|
|
password: EscapedString
|
|
email: EscapedString
|
|
enabled: Optional[bool] = 1
|
|
group_id: Optional[int] = 0
|
|
role_id: Annotated[int, Gt(0), Le(4)] = 4
|
|
|
|
|
|
class UserPut(BaseModel):
|
|
username: EscapedString
|
|
password: Optional[EscapedString] = ''
|
|
email: EscapedString
|
|
enabled: Optional[bool] = 1
|
|
|
|
|
|
class AddUserToGroup(BaseModel):
|
|
role_id: Annotated[int, Gt(0), Le(4)]
|
|
|
|
|
|
class ServerRequest(BaseModel):
|
|
hostname: EscapedString
|
|
ip: Union[IPvAnyAddress, DomainName]
|
|
enabled: Optional[bool] = 1
|
|
type_ip: Optional[bool] = 0
|
|
cred_id: int
|
|
description: Optional[EscapedString] = None
|
|
group_id: Optional[int] = None
|
|
protected: Optional[bool] = 0
|
|
master: Optional[int] = 0
|
|
port: Annotated[int, Gt(1), Le(65535)] = 22
|
|
haproxy: Optional[bool] = 0
|
|
nginx: Optional[bool] = 0
|
|
apache: Optional[bool] = 0
|
|
firewall_enable: Optional[bool] = 0
|
|
scan_server: Optional[bool] = 1
|
|
|
|
|
|
class GroupQuery(BaseModel):
|
|
group_id: Optional[int] = None
|
|
recurse: Optional[bool] = False
|
|
|
|
|
|
class GroupRequest(BaseModel):
|
|
name: EscapedString
|
|
description: Optional[EscapedString] = None
|
|
|
|
|
|
class CredRequest(BaseModel):
|
|
name: EscapedString
|
|
username: EscapedString
|
|
password: Optional[EscapedString] = None
|
|
key_enabled: Optional[bool] = 1
|
|
group_id: Optional[int] = None
|
|
shared: Optional[int] = 0
|
|
|
|
|
|
class CredUploadRequest(BaseModel):
|
|
private_key: Union[Base64Str, str]
|
|
passphrase: Optional[EscapedString] = None
|
|
|
|
|
|
class HAClusterServer(BaseModel):
|
|
eth: EscapedString
|
|
id: int
|
|
master: Optional[bool] = 1
|
|
|
|
|
|
class HAClusterServersRequest(BaseModel):
|
|
servers: List[HAClusterServer]
|
|
|
|
|
|
class HAClusterService(BaseModel):
|
|
enabled: Optional[bool] = 0
|
|
docker: Optional[bool] = 0
|
|
|
|
|
|
class HAClusterVIP(BaseModel):
|
|
use_src: Optional[bool] = 1
|
|
vip: IPvAnyAddress
|
|
return_master: Optional[bool] = 1
|
|
virt_server: Optional[bool] = 1
|
|
servers: List[HAClusterServer]
|
|
|
|
|
|
class HAClusterRequest(BaseModel):
|
|
name: EscapedString
|
|
description: Optional[EscapedString] = None
|
|
return_master: Optional[bool] = 1
|
|
servers: Optional[List[HAClusterServer]] = None
|
|
services: Dict[str, HAClusterService]
|
|
syn_flood: Optional[bool] = 1
|
|
use_src: Optional[bool] = 1
|
|
vip: Optional[IPvAnyAddress] = None
|
|
virt_server: Optional[bool] = 1
|
|
reconfigure: Optional[bool] = 0
|
|
|
|
|
|
class ConfigFileNameQuery(BaseModel):
|
|
file_path: Optional[str] = None
|
|
version: Optional[str] = None
|
|
|
|
|
|
class VersionsForDelete(BaseModel):
|
|
versions: List[str]
|
|
|
|
|
|
class ConfigRequest(BaseModel):
|
|
action: Literal['save', 'test', 'reload', 'restart']
|
|
file_path: Optional[str] = None
|
|
config_local_path: Optional[str] = None
|
|
config: str
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
login: EscapedString
|
|
password: EscapedString
|
|
|
|
|
|
class ChannelRequest(BaseModel):
|
|
token: EscapedString
|
|
channel: EscapedString
|
|
group_id: Optional[int] = None
|
|
|
|
|
|
class ServerInstall(BaseModel):
|
|
id: int
|
|
master: Optional[bool] = 0
|
|
|
|
|
|
class ServiceInstall(BaseModel):
|
|
cluster_id: Optional[int] = None
|
|
servers: Optional[List[ServerInstall]] = None
|
|
services: Optional[Dict[str, HAClusterService]] = None
|
|
checker: Optional[bool] = 0
|
|
metrics: Optional[bool] = 0
|
|
auto_start: Optional[bool] = 0
|
|
syn_flood: Optional[bool] = 0
|
|
docker: Optional[bool] = 0
|
|
|
|
|
|
class Checker(BaseModel):
|
|
checker: Optional[bool] = 0
|
|
metrics: Optional[bool] = 0
|
|
auto_start: Optional[bool] = 0
|
|
telegram_id: Optional[int] = 0
|
|
slack_id: Optional[int] = 0
|
|
pd_id: Optional[int] = 0
|
|
mm_id: Optional[int] = 0
|
|
email: Optional[int] = 1
|
|
service_alert: Optional[int] = 1
|
|
backend_alert: Optional[int] = 1
|
|
maxconn_alert: Optional[int] = 1
|
|
|
|
|
|
class SettingsRequest(BaseModel):
|
|
param: EscapedString
|
|
value: EscapedString
|
|
|
|
|
|
class BackupRequest(BaseModel):
|
|
cred_id: int
|
|
server_id: int
|
|
rserver: Optional[Union[IPvAnyAddress, DomainName]] = None
|
|
description: Optional[EscapedString] = None
|
|
rpath: Optional[EscapedString] = None
|
|
type: Literal['backup', 'synchronization'] = None
|
|
time: Literal['hourly', 'daily', 'weekly', 'monthly'] = None
|
|
|
|
|
|
class S3BackupRequest(BaseModel):
|
|
server_id: int
|
|
s3_server: Optional[Union[IPvAnyAddress, AnyUrl]] = None
|
|
bucket: EscapedString
|
|
secret_key: Optional[EscapedString] = None
|
|
access_key: Optional[EscapedString] = None
|
|
time: Literal['hourly', 'daily', 'weekly', 'monthly'] = None
|
|
description: Optional[EscapedString] = None
|
|
|
|
|
|
class GitBackupRequest(BaseModel):
|
|
server_id: int
|
|
service_id: int
|
|
init: Optional[bool] = 0
|
|
repo: Optional[EscapedString] = None
|
|
branch: Optional[EscapedString] = 'main'
|
|
time: Optional[EscapedString] = 'weekly'
|
|
cred_id: Optional[int] = None
|
|
description: Optional[EscapedString] = None
|
|
|
|
|
|
class PortScannerRequest(BaseModel):
|
|
enabled: Optional[bool] = 1
|
|
history: Optional[bool] = 1
|
|
notify: Optional[bool] = 1
|
|
|
|
|
|
class SSLCertUploadRequest(BaseModel):
|
|
server_ip: Union[IPvAnyAddress, DomainName]
|
|
name: EscapedString
|
|
cert: EscapedString
|
|
|
|
|
|
class SavedServerRequest(BaseModel):
|
|
server: EscapedString
|
|
description: Optional[EscapedString] = None
|
|
|
|
|
|
class LetsEncryptRequest(BaseModel):
|
|
server_id: int
|
|
domains: List[WildcardDomainName]
|
|
email: Optional[EmailStr] = None
|
|
type: Literal['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
|
api_key: Optional[EscapedString] = None
|
|
api_token: EscapedString
|
|
description: Optional[EscapedString] = None
|
|
|
|
@root_validator(pre=True)
|
|
@classmethod
|
|
def is_email_when_standalone(cls, values):
|
|
cert_type = ''
|
|
email = ''
|
|
if 'type' in values:
|
|
cert_type = values['type']
|
|
if 'email' in values:
|
|
email = values['email']
|
|
if cert_type == 'standalone' and email == '':
|
|
raise ValueError('Email must be when type is standalone')
|
|
return values
|
|
|
|
@root_validator(pre=True)
|
|
@classmethod
|
|
def is_api_key_when_route53(cls, values):
|
|
cert_type = ''
|
|
api_key = ''
|
|
if 'type' in values:
|
|
cert_type = values['type']
|
|
if 'api_key' in values:
|
|
api_key = values['api_key']
|
|
if cert_type == 'route53' and api_key == '':
|
|
raise ValueError('api_key(secret key) must be when type is route53')
|
|
return values
|
|
|
|
|
|
class LetsEncryptDeleteRequest(BaseModel):
|
|
server_id: int
|
|
domains: List[WildcardDomainName]
|
|
email: Optional[str] = None
|
|
type: Literal['standalone', 'route53', 'cloudflare', 'digitalocean', 'linode']
|
|
api_key: Optional[EscapedString] = None
|
|
api_token: EscapedString
|
|
description: Optional[EscapedString] = None
|
|
|
|
|
|
class HaproxyBinds(BaseModel):
|
|
ip: Optional[str] = None
|
|
port: Annotated[int, Gt(1), Le(65535)]
|
|
|
|
|
|
class HaproxyHeaders(BaseModel):
|
|
path: Literal['http-request', 'http-response']
|
|
name: str
|
|
method: Literal['add-header', 'set-header', 'del-header', 'replace-header', 'pass-header']
|
|
value: Optional[str] = None
|
|
|
|
|
|
class HaproxyAcls(BaseModel):
|
|
acl_if: int
|
|
acl_then: int
|
|
acl_then_value: Optional[str] = None
|
|
acl_value: str
|
|
|
|
|
|
class HaproxyBackendServer(BaseModel):
|
|
server: Union[IPvAnyAddress, DomainName]
|
|
port: Annotated[int, Gt(1), Le(65535)]
|
|
port_check: Annotated[int, Gt(1), Le(65535)]
|
|
maxconn: Optional[int] = 2000
|
|
send_proxy: Optional[bool] = 0
|
|
backup: Optional[bool] = 0
|
|
|
|
|
|
class HaproxyCookie(BaseModel):
|
|
dynamic: str
|
|
dynamic_key: str
|
|
domain: Optional[str] = None
|
|
name: Optional[str] = None
|
|
nocache: Optional[str] = None
|
|
postonly: Optional[str] = None
|
|
prefix: Optional[str] = None
|
|
rewrite: Optional[str] = None
|
|
|
|
|
|
class HaproxyHealthCheck(BaseModel):
|
|
check: str
|
|
domain: Optional[str] = None
|
|
path: str
|
|
|
|
|
|
class HaproxySSL(BaseModel):
|
|
cert: str
|
|
ssl_check_backend: Optional[bool] = 1
|
|
|
|
|
|
class HaproxyServersCheck(BaseModel):
|
|
check_enabled: Optional[bool] = 1
|
|
fall: Optional[int] = 5
|
|
rise: Optional[int] = 2
|
|
inter: Optional[int] = 2000
|
|
|
|
|
|
class HaproxyCircuitBreaking(BaseModel):
|
|
observe: Literal['layer7', 'layer4']
|
|
error_limit: int
|
|
on_error: Literal['mark-down', 'fastinter', 'fail-check', 'sudden-death']
|
|
|
|
|
|
class HaproxyConfigRequest(BaseModel):
|
|
balance: Optional[Literal['roundrobin', 'source', 'leastconn', 'first', 'rdp-cookie', 'uri', 'uri whole', 'static-rr']] = None
|
|
mode: Literal['tcp', 'http'] = 'http'
|
|
type: Literal['listen', 'frontend', 'backend']
|
|
name: EscapedString
|
|
option: Optional[str] = None
|
|
maxconn: Optional[int] = 2000
|
|
waf: Optional[bool] = False
|
|
binds: Optional[List[HaproxyBinds]] = None
|
|
headers: Optional[List[HaproxyHeaders]] = None
|
|
acls: Optional[List[HaproxyAcls]] = None
|
|
backend_servers: Optional[List[HaproxyBackendServer]] = None
|
|
blacklist: Optional[str] = ''
|
|
whitelist: Optional[str] = ''
|
|
ssl: Optional[HaproxySSL] = None
|
|
cache: Optional[bool] = False
|
|
compression: Optional[bool] = False
|
|
cookie: Optional[HaproxyCookie] = None
|
|
health_check: Optional[HaproxyHealthCheck] = None
|
|
servers_check: Optional[HaproxyServersCheck] = None
|
|
ssl_offloading: Optional[bool] = False
|
|
redispatch: Optional[bool] = False
|
|
forward_for: Optional[bool] = False
|
|
slow_attack: Optional[bool] = False
|
|
ddos: Optional[bool] = False
|
|
antibot: Optional[bool] = False
|
|
backends: Optional[str] = None
|
|
circuit_breaking: Optional[HaproxyCircuitBreaking] = None
|
|
action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save"
|
|
|
|
|
|
class HaproxyUserListUser(BaseModel):
|
|
user: str
|
|
password: str
|
|
group: Optional[str] = ''
|
|
|
|
|
|
class HaproxyUserListRequest(BaseModel):
|
|
name: EscapedString
|
|
type: Literal['userlist']
|
|
userlist_users: Optional[List[HaproxyUserListUser]] = ''
|
|
userlist_groups: Optional[List[str]] = ''
|
|
action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save"
|
|
|
|
|
|
class HaproxyPeers(BaseModel):
|
|
name: str
|
|
ip: Union[IPvAnyAddress, DomainName]
|
|
port: Annotated[int, Gt(1), Le(65535)]
|
|
|
|
|
|
class HaproxyPeersRequest(BaseModel):
|
|
name: EscapedString
|
|
type: Literal['peers']
|
|
peers: List[HaproxyPeers]
|
|
action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save"
|
|
|
|
|
|
class GenerateConfigRequest(BaseModel):
|
|
generate: Optional[bool] = 0
|
|
|
|
|
|
class HaproxyGlobalRequest(BaseModel):
|
|
log: Optional[List[str]] = ['127.0.0.1 local', '127.0.0.1 local1 notice']
|
|
chroot: Optional[str] = '/var/lib/haproxy'
|
|
pidfile: Optional[str] = '/var/run/haproxy.pid'
|
|
maxconn: Optional[int] = 5000
|
|
user: Optional[str] = 'haproxy'
|
|
group: Optional[str] = 'haproxy'
|
|
daemon: Optional[bool] = 1
|
|
socket: Optional[List[str]] = ['*:1999 level admin', '/var/run/haproxy.sock mode 600 level admin', '/var/lib/haproxy/stats']
|
|
type: Optional[Literal['global']] = 'global'
|
|
option: Optional[str] = ''
|
|
action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save"
|
|
|
|
|
|
class HaproxyDefaultsTimeout(BaseModel):
|
|
http_request: Optional[int] = 10
|
|
queue: Optional[int] = 60
|
|
connect: Optional[int] = 10
|
|
client: Optional[int] = 60
|
|
server: Optional[int] = 60
|
|
check: Optional[int] = 10
|
|
http_keep_alive: Optional[int] = 10
|
|
|
|
|
|
class HaproxyDefaultsRequest(BaseModel):
|
|
log: Optional[str] = 'global'
|
|
retries: Optional[int] = 3
|
|
timeout: Optional[HaproxyDefaultsTimeout] = HaproxyDefaultsTimeout().model_dump(mode='json')
|
|
option: Optional[str] = ''
|
|
maxconn: Optional[int] = 5000
|
|
type: Optional[Literal['defaults']] = 'defaults'
|
|
action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save"
|