2022-08-12 11:55:14 +01:00
|
|
|
import os.path
|
2022-09-26 13:40:59 +01:00
|
|
|
import sys
|
2022-05-16 11:44:03 +01:00
|
|
|
from abc import abstractmethod
|
2022-05-08 13:01:15 +01:00
|
|
|
import datetime
|
2022-09-26 13:40:59 +01:00
|
|
|
from collections import defaultdict
|
|
|
|
from typing import Optional, Any, List, Dict
|
2022-03-10 14:26:22 +00:00
|
|
|
|
2022-10-23 12:14:29 +01:00
|
|
|
from flask import current_app
|
2022-04-25 15:06:24 +01:00
|
|
|
from sqlalchemy import text
|
|
|
|
|
2022-03-10 14:26:22 +00:00
|
|
|
from app import app
|
|
|
|
from app.extensions import db
|
2022-04-22 14:01:16 +01:00
|
|
|
from app.models.base import Group
|
2022-05-24 19:51:38 +01:00
|
|
|
from app.models.mirrors import Proxy, Origin, SmartProxy
|
2022-05-08 13:01:15 +01:00
|
|
|
from app.terraform.terraform import TerraformAutomation
|
|
|
|
|
2022-03-10 14:26:22 +00:00
|
|
|
|
2022-05-24 19:51:38 +01:00
|
|
|
def update_smart_proxy_instance(group_id: int,
|
|
|
|
provider: str,
|
|
|
|
region: str,
|
|
|
|
instance_id: str) -> None:
|
|
|
|
instance = SmartProxy.query.filter(
|
|
|
|
SmartProxy.group_id == group_id,
|
|
|
|
SmartProxy.region == region,
|
|
|
|
SmartProxy.provider == provider,
|
|
|
|
SmartProxy.destroyed.is_(None)
|
|
|
|
).first()
|
|
|
|
if instance is None:
|
|
|
|
instance = SmartProxy()
|
2024-11-16 19:38:57 +00:00
|
|
|
instance.added = datetime.datetime.now(datetime.UTC)
|
2022-05-24 19:51:38 +01:00
|
|
|
instance.group_id = group_id
|
|
|
|
instance.provider = provider
|
|
|
|
instance.region = region
|
|
|
|
db.session.add(instance)
|
2024-11-16 19:38:57 +00:00
|
|
|
instance.updated = datetime.datetime.now(datetime.UTC)
|
2022-05-24 19:51:38 +01:00
|
|
|
instance.instance_id = instance_id
|
|
|
|
|
|
|
|
|
2022-05-08 13:01:15 +01:00
|
|
|
class ProxyAutomation(TerraformAutomation):
|
2022-09-26 13:40:59 +01:00
|
|
|
subgroup_members_max = sys.maxsize
|
2022-05-16 11:44:03 +01:00
|
|
|
"""
|
|
|
|
Maximum number of proxies to deploy per sub-group. This is required for some providers
|
|
|
|
where the number origins per group may exceed the number of proxies that can be configured
|
|
|
|
in a single "configuration block", e.g. Azure CDN's profiles.
|
|
|
|
"""
|
|
|
|
|
2022-09-26 13:40:59 +01:00
|
|
|
subgroup_count_max = sys.maxsize
|
|
|
|
"""
|
|
|
|
Maximum number of subgroups that can be deployed. This is required for some providers where
|
|
|
|
the total number of subgroups is limited by a quota, e.g. Azure CDN's profiles.
|
|
|
|
"""
|
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
template: str
|
|
|
|
"""
|
|
|
|
Terraform configuration template using Jinja 2.
|
|
|
|
"""
|
|
|
|
|
|
|
|
template_parameters: List[str]
|
|
|
|
"""
|
|
|
|
List of parameters to be read from the application configuration for use
|
|
|
|
in the templating of the Terraform configuration.
|
|
|
|
"""
|
2022-03-10 14:26:22 +00:00
|
|
|
|
2022-05-24 19:51:38 +01:00
|
|
|
smart_proxies = False
|
|
|
|
"""
|
|
|
|
Whether this provider supports "smart" proxies.
|
|
|
|
"""
|
|
|
|
|
2022-10-23 12:14:29 +01:00
|
|
|
cloud_name: str
|
|
|
|
"""
|
|
|
|
The name of the cloud provider used by this proxy provider. Used to determine if this provider can be enabled.
|
|
|
|
"""
|
|
|
|
|
2023-02-26 12:52:08 +00:00
|
|
|
provider: str # type: ignore[assignment]
|
|
|
|
# TODO: Temporary override
|
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
@abstractmethod
|
|
|
|
def import_state(self, state: Any) -> None:
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2022-10-23 12:14:29 +01:00
|
|
|
def enabled(self) -> bool:
|
2024-07-12 15:35:04 +01:00
|
|
|
return bool(current_app.config.get(self.cloud_name.upper() + "_ENABLED", False))
|
2022-10-23 12:14:29 +01:00
|
|
|
|
2022-06-17 14:26:22 +01:00
|
|
|
def tf_prehook(self) -> Optional[Any]: # pylint: disable=useless-return
|
|
|
|
return None
|
2022-05-08 10:47:01 +01:00
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
def tf_posthook(self, *, prehook_result: Any = None) -> None:
|
2022-05-08 13:01:15 +01:00
|
|
|
self.import_state(self.tf_show())
|
|
|
|
|
2022-05-16 11:44:03 +01:00
|
|
|
def tf_generate(self) -> None:
|
2022-05-24 19:51:38 +01:00
|
|
|
groups = Group.query.all()
|
2022-05-08 13:01:15 +01:00
|
|
|
self.tf_write(
|
2022-03-10 14:26:22 +00:00
|
|
|
self.template,
|
2022-05-24 19:51:38 +01:00
|
|
|
groups=groups,
|
2022-03-10 14:26:22 +00:00
|
|
|
proxies=Proxy.query.filter(
|
2023-05-30 16:38:00 +01:00
|
|
|
Proxy.provider == self.provider, Proxy.destroyed.is_(None)).all(),
|
|
|
|
subgroups=self.get_subgroups(),
|
2022-05-24 19:51:38 +01:00
|
|
|
global_namespace=app.config['GLOBAL_NAMESPACE'], bypass_token=app.config['BYPASS_TOKEN'],
|
2022-08-12 11:55:14 +01:00
|
|
|
terraform_modules_path=os.path.join(*list(os.path.split(app.root_path))[:-1], 'terraform-modules'),
|
2022-08-30 10:05:12 +01:00
|
|
|
backend_config=f"""backend "http" {{
|
|
|
|
lock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
|
|
|
|
unlock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
|
|
|
|
address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
|
|
|
|
}}""",
|
2022-05-24 19:51:38 +01:00
|
|
|
**{k: app.config[k.upper()] for k in self.template_parameters})
|
|
|
|
if self.smart_proxies:
|
|
|
|
for group in groups:
|
|
|
|
self.sp_config(group)
|
|
|
|
|
|
|
|
def sp_config(self, group: Group) -> None:
|
|
|
|
group_origins: List[Origin] = Origin.query.filter(
|
|
|
|
Origin.group_id == group.id,
|
|
|
|
Origin.destroyed.is_(None),
|
|
|
|
Origin.smart.is_(True)
|
|
|
|
).all()
|
|
|
|
self.tmpl_write(f"smart_proxy.{group.id}.conf", """
|
|
|
|
{% for origin in origins %}
|
|
|
|
server {
|
|
|
|
listen 443 ssl;
|
2023-04-26 15:53:10 +01:00
|
|
|
server_name origin-{{ origin.id }}.{{ origin.group.group_name | lower }}.smart.{{ smart_zone[:-1] }};
|
2024-03-08 12:02:44 +00:00
|
|
|
resolver 1.1.1.1;
|
2024-03-12 12:19:38 +00:00
|
|
|
location = /english/utag.js {
|
|
|
|
proxy_set_header Accept-Encoding "";
|
|
|
|
proxy_ssl_server_name on;
|
|
|
|
proxy_pass https://{{ origin.domain_name }}/english/utag.js;
|
|
|
|
subs_filter_types text/javascript application/javascript;
|
|
|
|
subs_filter https://{{ origin.domain_name }}/ /;
|
|
|
|
subs_filter "(https:)?//tags.{{ origin.normalised_domain_name }}/" / r;
|
2024-04-02 13:56:15 +01:00
|
|
|
subs_filter "(https:)?//ssc.{{ origin.normalised_domain_name }}/" / r;
|
|
|
|
|
2024-03-12 12:19:38 +00:00
|
|
|
}
|
2024-02-20 11:48:59 +00:00
|
|
|
location ~ (.+)/utag(.+)js$ {
|
2024-02-19 12:20:35 +00:00
|
|
|
proxy_set_header Accept-Encoding "";
|
|
|
|
proxy_ssl_server_name on;
|
2024-02-20 11:48:59 +00:00
|
|
|
proxy_pass https://tags.tiqcdn.com/utag/bbg/$1/utag$2js;
|
2024-04-02 15:16:01 +01:00
|
|
|
subs_filter_types text/html text/css text/xml application/javascript image/gif;
|
2024-02-20 11:48:59 +00:00
|
|
|
subs_filter //tags.tiqcdn.com/utag/bbg/ /;
|
2024-03-12 11:23:49 +00:00
|
|
|
subs_filter https://{{ origin.domain_name }}/ /;
|
|
|
|
subs_filter "(https:)?//tags.{{ origin.normalised_domain_name }}/" / r;
|
2024-04-02 13:56:15 +01:00
|
|
|
subs_filter "(https:)?//ssc.{{ origin.normalised_domain_name }}/" / r;
|
2024-04-17 15:27:45 +01:00
|
|
|
subs_filter return"http"+(a.ssl?"s":"")+"://"+b+"/b/ss/ return"/b/ss/;
|
|
|
|
}
|
2024-04-02 13:56:15 +01:00
|
|
|
|
2024-04-17 15:27:45 +01:00
|
|
|
location ~ /b/ss/(.+)$ {
|
|
|
|
proxy_set_header Accept-Encoding "";
|
|
|
|
proxy_ssl_server_name on;
|
|
|
|
proxy_pass https://bbg.sc.omtrdc.net/b/ss/$1;
|
2024-05-10 11:15:41 +01:00
|
|
|
proxy_intercept_errors on;
|
|
|
|
error_page 302 = @handle_redirects;
|
|
|
|
}
|
|
|
|
location @handle_redirects {
|
|
|
|
set $saved_redirect_location '$upstream_http_location';
|
|
|
|
proxy_pass $saved_redirect_location;
|
2024-04-02 13:56:15 +01:00
|
|
|
}
|
2022-05-24 19:51:38 +01:00
|
|
|
location / {
|
|
|
|
proxy_set_header Accept-Encoding "";
|
|
|
|
proxy_ssl_server_name on;
|
|
|
|
proxy_pass https://{{ origin.domain_name }}/;
|
2024-04-02 15:16:01 +01:00
|
|
|
subs_filter_types text/html text/css text/xml image/gif;
|
2022-05-24 19:51:38 +01:00
|
|
|
subs_filter https://{{ origin.domain_name }}/ /;
|
2024-02-20 11:48:59 +00:00
|
|
|
subs_filter "(https:)?//tags.{{ origin.normalised_domain_name }}/" / r;
|
2024-04-02 13:56:15 +01:00
|
|
|
subs_filter "(https:)?//ssc.{{ origin.normalised_domain_name }}/" / r;
|
2022-05-25 15:57:00 +01:00
|
|
|
{%- for asset_origin in origin.group.origins | selectattr("assets") -%}
|
2023-04-26 15:53:10 +01:00
|
|
|
{%- for asset_proxy in asset_origin.proxies | selectattr("deprecated", "none") | selectattr("destroyed", "none") -%}
|
2022-05-25 15:57:00 +01:00
|
|
|
{%- if loop.first %}
|
2022-05-27 15:24:34 +01:00
|
|
|
subs_filter https://{{ asset_origin.domain_name }}/ {{ asset_proxy.url }}/;
|
2022-05-25 15:57:00 +01:00
|
|
|
{%- endif -%}
|
|
|
|
{%- endfor -%}
|
|
|
|
{%- endfor %}
|
2022-05-24 19:51:38 +01:00
|
|
|
}
|
|
|
|
ssl_certificate /etc/ssl/smart_proxy.crt;
|
|
|
|
ssl_certificate_key /etc/ssl/private/smart_proxy.key;
|
|
|
|
}
|
|
|
|
{% endfor %}
|
|
|
|
""",
|
|
|
|
provider=self.provider,
|
2022-05-25 15:32:17 +01:00
|
|
|
origins=group_origins,
|
|
|
|
smart_zone=app.config['SMART_ZONE'])
|
2022-09-26 13:40:59 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_subgroups(cls) -> Dict[int, Dict[int, int]]:
|
|
|
|
conn = db.engine.connect()
|
2023-03-13 13:46:38 +00:00
|
|
|
stmt = text("""
|
2022-09-26 13:40:59 +01:00
|
|
|
SELECT origin.group_id, proxy.psg, COUNT(proxy.id) FROM proxy, origin
|
|
|
|
WHERE proxy.origin_id = origin.id
|
|
|
|
AND proxy.destroyed IS NULL
|
|
|
|
AND proxy.provider = :provider
|
|
|
|
GROUP BY origin.group_id, proxy.psg;
|
2023-03-13 13:46:38 +00:00
|
|
|
""")
|
|
|
|
stmt = stmt.bindparams(provider=cls.provider)
|
|
|
|
result = conn.execute(stmt).all()
|
2022-09-26 13:40:59 +01:00
|
|
|
subgroups: Dict[int, Dict[int, int]] = defaultdict(lambda: defaultdict(lambda: 0))
|
|
|
|
for row in result:
|
|
|
|
subgroups[row[0]][row[1]] = row[2]
|
|
|
|
return subgroups
|