3636logger = logging .getLogger (__name__ )
3737
3838# Files and constants
39- NFT_CONF_DIR = Path ("/etc /aproxy" )
39+ NFT_CONF_DIR = Path ("/opt /aproxy-charm " )
4040NFT_CONF_FILE = NFT_CONF_DIR / "nftables.conf"
4141SYSTEMD_UNIT_PATH = Path ("/etc/systemd/system/aproxy-nftables.service" )
4242APROXY_LISTEN_PORT = 8443
4343APROXY_SNAP_NAME = "aproxy"
4444APROXY_SNAP_CHANNEL = "edge"
45- DEFAULT_PROXY_PORT = 3128
45+ DEFAULT_PROXY_PORT = 80
4646RELATION_NAME = "juju-info"
4747
4848# ^\.? : one leading dot allowed
5555 r"^\.?(?!-)(?!.*--)(?!.*-$)([a-zA-Z0-9-]{1,63}\.)*([a-zA-Z0-9-]{1,63})\.?$"
5656)
5757
58+ # ^ : start of the string
59+ # [a-zA-Z] : match letter
60+ # [a-zA-Z0-9+\-.]* : match letters, digits, +, -, ., zero or more times
61+ # :// : match literal "://"
62+ URI_SCHEME_PREFIX_RE = re .compile (r"^[a-zA-Z][a-zA-Z0-9+\-.]*://" )
63+
5864
5965class AproxyConfig (BaseModel ):
6066 """Configuration model for aproxy charm.
@@ -63,7 +69,7 @@ class AproxyConfig(BaseModel):
6369 model_config: Pydantic config to forbid extra fields.
6470 proxy_address: The target proxy address (hostname or IP).
6571 proxy_port: The target proxy port.
66- no_proxy : Comma-separated list of IPs, CIDRs, or hostnames to
72+ exclude_addresses : Comma-separated list of IPs, CIDRs, or hostnames to
6773 exclude from interception.
6874 intercept_ports_list: List of ports to intercept as strings.
6975 """
@@ -72,7 +78,7 @@ class AproxyConfig(BaseModel):
7278
7379 proxy_address : str
7480 proxy_port : int = DEFAULT_PROXY_PORT
75- no_proxy : List [str ] = []
81+ exclude_addresses : List [str ] = []
7682 intercept_ports_list : List [str ]
7783
7884 @classmethod
@@ -106,10 +112,12 @@ def from_charm(cls, charm: ops.CharmBase) -> "AproxyConfig":
106112 ) from exc
107113
108114 # Parse no proxy list
109- no_proxy = []
110- raw_no_proxy = str (conf .get ("exclude-addresses-from-proxy" , "" ))
111- if raw_no_proxy :
112- no_proxy = [entry .strip () for entry in raw_no_proxy .split ("," ) if entry .strip ()]
115+ exclude_addresses = []
116+ raw_exclude_addresses = str (conf .get ("exclude-addresses-from-proxy" , "" ))
117+ if raw_exclude_addresses :
118+ exclude_addresses = [
119+ entry .strip () for entry in raw_exclude_addresses .split ("," ) if entry .strip ()
120+ ]
113121
114122 # Parse intercept ports
115123 intercept_ports_raw = str (conf .get ("intercept-ports" , "" ))
@@ -120,7 +128,7 @@ def from_charm(cls, charm: ops.CharmBase) -> "AproxyConfig":
120128 return cls (
121129 proxy_address = proxy_address ,
122130 proxy_port = proxy_port ,
123- no_proxy = no_proxy ,
131+ exclude_addresses = exclude_addresses ,
124132 intercept_ports_list = intercept_ports_list ,
125133 )
126134 except ValidationError as exc :
@@ -146,25 +154,25 @@ def _validate_proxy_port(cls, proxy_port: int) -> int:
146154 raise ValueError (f"proxy port must be between 1 and 65535 instead of { proxy_port } " )
147155 return proxy_port
148156
149- @field_validator ("no_proxy " )
150- def _validate_no_proxy (cls , no_proxy : List [str ]) -> List [str ]:
151- """Validate no_proxy entries are valid IPs, CIDRs, or hostnames."""
152- valid_no_proxy = []
153- for entry in no_proxy :
157+ @field_validator ("exclude_addresses " )
158+ def _validate_exclude_addresses (cls , exclude_addresses : List [str ]) -> List [str ]:
159+ """Validate exclude_addresses entries are valid IPs, CIDRs, or hostnames."""
160+ valid_exclude_addresses = []
161+ for entry in exclude_addresses :
154162 entry = entry .strip ()
155163 if not entry :
156164 continue
157165 try :
158166 # Check if it's a valid IP or CIDR
159167 ipaddress .ip_network (entry , strict = False )
160- valid_no_proxy .append (entry )
168+ valid_exclude_addresses .append (entry )
161169 except ValueError as exc :
162170 # If not an IP/CIDR, check if it's a valid hostname
163171 if HOSTNAME_PATTERN .match (entry ):
164- valid_no_proxy .append (entry )
172+ valid_exclude_addresses .append (entry )
165173 else :
166- raise ValueError (f"invalid no_proxy entry { entry } " ) from exc
167- return valid_no_proxy
174+ raise ValueError (f"invalid exclude_addresses entry { entry } " ) from exc
175+ return valid_exclude_addresses
168176
169177 @field_validator ("intercept_ports_list" )
170178 def _validate_and_merge_ports (cls , ports : List [str ]) -> List [str ]:
@@ -228,8 +236,8 @@ def _get_principal_proxy_address(cls) -> str:
228236
229237 proxy_conf = https_proxy or http_proxy or ""
230238
231- # Strip any leading http:// or https://
232- return re .sub (r"^https?://" , "" , proxy_conf )
239+ # Strip prefix like http://, https://, socks5h://, ftp://, etc.
240+ return URI_SCHEME_PREFIX_RE .sub ("" , proxy_conf )
233241
234242
235243class AproxyManager :
@@ -276,14 +284,23 @@ def configure_target_proxy(self) -> None:
276284 Raises:
277285 ConnectionError: If the target proxy is not reachable.
278286 """
287+ snap_cache = snap .SnapCache ()
288+ aproxy_snap = snap_cache [APROXY_SNAP_NAME ]
289+
290+ # Check if current config is the same as the target config
291+ current_proxy = aproxy_snap .get ("proxy" )
279292 target_proxy = f"{ self .config .proxy_address } :{ self .config .proxy_port } "
293+ if current_proxy == target_proxy :
294+ logger .info ("Proxy is already set to %s, skipping reconfiguration" , target_proxy )
295+ return
296+
297+ # Check if target proxy is reachable
280298 if not self ._is_proxy_reachable (self .config .proxy_address , self .config .proxy_port ):
281299 logger .error ("Proxy is not reachable at %s" , target_proxy )
282300 raise ConnectionError (f"Proxy is not reachable at { target_proxy } " )
283301
284302 logger .info ("Configuring snap: proxy=%s" , target_proxy )
285- snap_cache = snap .SnapCache ()
286- snap_cache [APROXY_SNAP_NAME ].set ({"proxy" : target_proxy })
303+ aproxy_snap .set ({"proxy" : target_proxy })
287304
288305 def _is_proxy_reachable (self , host : str , port : int = DEFAULT_PROXY_PORT ) -> bool :
289306 """Check if the target proxy is reachable on the specified port.
@@ -342,7 +359,7 @@ def _render_nft_rules(self) -> str:
342359 [
343360 "127.0.0.0/8" , # private loopback range
344361 ]
345- + self .config .no_proxy
362+ + self .config .exclude_addresses
346363 )
347364
348365 return f"""#!/usr/sbin/nft -f
0 commit comments