Skip to content

Commit cea6125

Browse files
authored
Add Firewall support (#111)
Signed-off-by: Lukas Kämmerling <lukas.kaemmerling@hetzner-cloud.de>
1 parent 6d67a86 commit cea6125

15 files changed

Lines changed: 1326 additions & 33 deletions

File tree

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ python38:
4444

4545
python39:
4646
<<: *tests_template
47-
image: python:3.9-rc-alpine
47+
image: python:3.9-alpine
4848
script: tox -e py39
4949

5050
test-style:

docs/api.clients.firewalls.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FirewallsClient
2+
==================
3+
4+
5+
.. autoclass:: hcloud.firewalls.client.FirewallsClient
6+
:members:
7+
8+
.. autoclass:: hcloud.firewalls.client.BoundFirewall
9+
:members:
10+
11+
.. autoclass:: hcloud.firewalls.domain.Firewall
12+
:members:
13+
14+
.. autoclass:: hcloud.firewalls.domain.FirewallRule
15+
:members:
16+
17+
.. autoclass:: hcloud.firewalls.domain.FirewallResource
18+
:members:
19+
20+
.. autoclass:: hcloud.firewalls.domain.CreateFirewallResponse
21+
:members:
22+

hcloud/firewalls/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

hcloud/firewalls/client.py

Lines changed: 361 additions & 0 deletions
Large diffs are not rendered by default.

hcloud/firewalls/domain.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# -*- coding: utf-8 -*-
2+
from dateutil.parser import isoparse
3+
4+
from hcloud.core.domain import BaseDomain
5+
6+
7+
class Firewall(BaseDomain):
8+
"""Firewall Domain
9+
10+
:param id: int
11+
ID of the Firewall
12+
:param name: str
13+
Name of the Firewall
14+
:param labels: dict
15+
User-defined labels (key-value pairs)
16+
:param rules: List[:class:`FirewallRule <hcloud.firewalls.domain.FirewallRule>`]
17+
Rules of the Firewall
18+
:param applied_to: List[:class:`FirewallResource <hcloud.firewalls.domain.FirewallResource>`]
19+
Resources currently using the Firewall
20+
:param created: datetime
21+
Point in time when the image was created
22+
"""
23+
__slots__ = (
24+
"id",
25+
"name",
26+
"labels",
27+
"rules",
28+
"applied_to",
29+
"created"
30+
)
31+
32+
def __init__(
33+
self,
34+
id=None,
35+
name=None,
36+
labels=None,
37+
rules=None,
38+
applied_to=None,
39+
created=None
40+
):
41+
self.id = id
42+
self.name = name
43+
self.rules = rules
44+
self.applied_to = applied_to
45+
self.labels = labels
46+
self.created = isoparse(created) if created else None
47+
48+
49+
class FirewallRule:
50+
"""Firewall Rule Domain
51+
52+
:param direction: str
53+
The Firewall which was created
54+
:param port: str
55+
Port to which traffic will be allowed, only applicable for protocols TCP and UDP, specify port ranges by using
56+
- as a indicator, Sample: 80-85 means all ports between 80 & 85 (80, 82, 83, 84, 85)
57+
:param protocol: str
58+
Select traffic direction on which rule should be applied. Use source_ips for direction in and destination_ips for direction out.
59+
:param source_ips: List[str]
60+
List of permitted IPv4/IPv6 addresses in CIDR notation. Use 0.0.0.0/0 to allow all IPv4 addresses and ::/0 to allow all IPv6 addresses. You can specify 100 CIDRs at most.
61+
:param destination_ips: List[str]
62+
List of permitted IPv4/IPv6 addresses in CIDR notation. Use 0.0.0.0/0 to allow all IPv4 addresses and ::/0 to allow all IPv6 addresses. You can specify 100 CIDRs at most.
63+
"""
64+
__slots__ = (
65+
"direction",
66+
"port",
67+
"protocol",
68+
"source_ips",
69+
"destination_ips"
70+
)
71+
72+
DIRECTION_IN = "in"
73+
"""Firewall Rule Direction In"""
74+
DIRECTION_OUT = "out"
75+
"""Firewall Rule Direction Out"""
76+
77+
PROTOCOL_UDP = "udp"
78+
"""Firewall Rule Protocol UDP"""
79+
PROTOCOL_ICMP = "icmp"
80+
"""Firewall Rule Protocol ICMP"""
81+
PROTOCOL_TCP = "tcp"
82+
"""Firewall Rule Protocol TCP"""
83+
84+
def __init__(
85+
self,
86+
direction, # type: str
87+
protocol, # type: str
88+
source_ips, # type: List[str]
89+
port=None, # type: Optional[str]
90+
destination_ips=[], # type: Optional[List[str]]
91+
):
92+
self.direction = direction
93+
self.port = port
94+
self.protocol = protocol
95+
self.source_ips = source_ips
96+
self.destination_ips = destination_ips
97+
98+
def to_payload(self):
99+
payload = {
100+
"direction": self.direction,
101+
"protocol": self.protocol,
102+
"source_ips": self.source_ips,
103+
}
104+
if len(self.destination_ips) > 0:
105+
payload.update({"destination_ips": self.destination_ips})
106+
if self.port is not None:
107+
payload.update({"port": self.port})
108+
return payload
109+
110+
111+
class FirewallResource:
112+
"""Firewall Used By Domain
113+
114+
:param type: str
115+
Type of resource referenced
116+
:param server: Optional[Server]
117+
Server the Firewall is applied to
118+
"""
119+
__slots__ = (
120+
"type",
121+
"server",
122+
)
123+
124+
TYPE_SERVER = "server"
125+
"""Firewall Used By Type Server"""
126+
127+
def __init__(
128+
self,
129+
type, # type: str
130+
server=None, # type: Optional[Server]
131+
):
132+
self.type = type
133+
self.server = server
134+
135+
def to_payload(self):
136+
payload = {
137+
"type": self.type,
138+
}
139+
if self.server is not None:
140+
payload.update({"server": {"id": self.server.id}})
141+
return payload
142+
143+
144+
class CreateFirewallResponse(BaseDomain):
145+
"""Create Firewall Response Domain
146+
147+
:param firewall: :class:`BoundFirewall <hcloud.firewalls.client.BoundFirewall>`
148+
The Firewall which was created
149+
:param action: :class:`BoundAction <hcloud.actions.client.BoundAction>`
150+
The Action which shows the progress of the Firewall Creation
151+
"""
152+
__slots__ = (
153+
"firewall",
154+
"action"
155+
)
156+
157+
def __init__(
158+
self,
159+
firewall, # type: BoundFirewall
160+
action, # type: BoundAction
161+
):
162+
self.firewall = firewall
163+
self.action = action

hcloud/hcloud.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from hcloud.load_balancer_types.client import LoadBalancerTypesClient
2121

2222
from .__version__ import VERSION
23+
from .firewalls.client import FirewallsClient
2324

2425

2526
class APIException(Exception):
@@ -133,6 +134,12 @@ def __init__(self, token, api_endpoint="https://api.hetzner.cloud/v1", applicati
133134
:type: :class:`LoadBalancerTypesClient <hcloud.load_balancer_types.client.LoadBalancerTypesClient>`
134135
"""
135136

137+
self.firewalls = FirewallsClient(self)
138+
"""FirewallsClient Instance
139+
140+
:type: :class:`FirewallsClient <hcloud.firewalls.client.FirewallsClient>`
141+
"""
142+
136143
def _get_user_agent(self):
137144
"""Get the user agent of the hcloud-python instance with the user application name (if specified)
138145

hcloud/load_balancers/domain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class LoadBalancer(BaseDomain):
2525
Private networks information.
2626
:param algorithm: LoadBalancerAlgorithm
2727
The algorithm the Load Balancer is currently using
28-
:param services: LoadBalancerService
28+
:param services: List[LoadBalancerService]
2929
The services the LoadBalancer is currently serving
3030
:param targets: LoadBalancerTarget
3131
The targets the LoadBalancer is currently serving

hcloud/servers/client.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
from hcloud.actions.client import BoundAction
55
from hcloud.core.domain import add_meta_to_result
6+
from hcloud.firewalls.client import BoundFirewall
67
from hcloud.floating_ips.client import BoundFloatingIP
78
from hcloud.isos.client import BoundIso
89
from hcloud.servers.domain import Server, CreateServerResponse, ResetPasswordResponse, EnableRescueResponse, \
9-
RequestConsoleResponse, PublicNetwork, IPv4Address, IPv6Network, PrivateNet
10+
RequestConsoleResponse, PublicNetwork, IPv4Address, IPv6Network, PrivateNet, PublicNetworkFirewall
1011
from hcloud.volumes.client import BoundVolume
1112
from hcloud.images.domain import CreateImageResponse
1213
from hcloud.images.client import BoundImage
@@ -48,11 +49,21 @@ def __init__(self, client, data, complete=True):
4849
ipv6_network = IPv6Network(**public_net['ipv6'])
4950
floating_ips = [BoundFloatingIP(client._client.floating_ips, {"id": floating_ip}, complete=False) for
5051
floating_ip in public_net['floating_ips']]
51-
data['public_net'] = PublicNetwork(ipv4=ipv4_address, ipv6=ipv6_network, floating_ips=floating_ips)
52+
firewalls = [
53+
PublicNetworkFirewall(
54+
BoundFirewall(client._client.firewalls, {"id": firewall["id"]}, complete=False),
55+
status=firewall["status"]
56+
) for firewall in public_net.get("firewalls", [])
57+
]
58+
data['public_net'] = PublicNetwork(ipv4=ipv4_address, ipv6=ipv6_network, floating_ips=floating_ips,
59+
firewalls=firewalls)
5260

5361
private_nets = data.get("private_net")
5462
if private_nets:
55-
private_nets = [PrivateNet(network=BoundNetwork(client._client.networks, {"id": private_net['network']}, complete=False), ip=private_net['ip'], alias_ips=private_net['alias_ips'], mac_address=private_net['mac_address']) for private_net in private_nets]
63+
private_nets = [PrivateNet(
64+
network=BoundNetwork(client._client.networks, {"id": private_net['network']}, complete=False),
65+
ip=private_net['ip'], alias_ips=private_net['alias_ips'], mac_address=private_net['mac_address']) for
66+
private_net in private_nets]
5667
data['private_net'] = private_nets
5768

5869
super(BoundServer, self).__init__(client, data, complete)
@@ -392,6 +403,7 @@ def create(self,
392403
image, # type: Image
393404
ssh_keys=None, # type: Optional[List[SSHKey]]
394405
volumes=None, # type: Optional[List[Volume]]
406+
firewalls=None, # type: Optional[List[Firewall]]
395407
networks=None, # type: Optional[List[Network]]
396408
user_data=None, # type: Optional[str]
397409
labels=None, # type: Optional[Dict[str, str]]
@@ -444,6 +456,8 @@ def create(self,
444456
data['volumes'] = [volume.id for volume in volumes]
445457
if networks is not None:
446458
data['networks'] = [network.id for network in networks]
459+
if firewalls is not None:
460+
data['firewalls'] = [firewall.id for firewall in firewalls]
447461
if user_data is not None:
448462
data['user_data'] = user_data
449463
if labels is not None:

hcloud/servers/domain.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,21 +239,49 @@ class PublicNetwork(BaseDomain):
239239
:param ipv4: :class:`IPv4Address <hcloud.servers.domain.IPv4Address>`
240240
:param ipv6: :class:`IPv6Network <hcloud.servers.domain.IPv6Network>`
241241
:param floating_ips: List[:class:`BoundFloatingIP <hcloud.floating_ips.client.BoundFloatingIP>`]
242+
:param firewalls: List[:class:`PublicNetworkFirewall <hcloud.servers.client.PublicNetworkFirewall>`]
242243
"""
243244
__slots__ = (
244245
"ipv4",
245246
"ipv6",
246-
"floating_ips"
247+
"floating_ips",
248+
"firewalls"
247249
)
248250

249251
def __init__(self,
250252
ipv4, # type: IPv4Address
251253
ipv6, # type: IPv6Network
252254
floating_ips, # type: List[BoundFloatingIP]
255+
firewalls=None, # type: List[PublicNetworkFirewall]
253256
):
254257
self.ipv4 = ipv4
255258
self.ipv6 = ipv6
256259
self.floating_ips = floating_ips
260+
self.firewalls = firewalls
261+
262+
263+
class PublicNetworkFirewall(BaseDomain):
264+
"""Public Network Domain
265+
266+
:param firewall: :class:`BoundFirewall <hcloud.firewalls.domain.BoundFirewall>`
267+
:param status: str
268+
"""
269+
__slots__ = (
270+
"firewall",
271+
"status"
272+
)
273+
274+
STATUS_APPLIED = "applied"
275+
"""Public Network Firewall Status applied"""
276+
STATUS_PENDING = "pending"
277+
"""Public Network Firewall Status pending"""
278+
279+
def __init__(self,
280+
firewall, # type: BoundFirewall
281+
status, # type: str
282+
):
283+
self.firewall = firewall
284+
self.status = status
257285

258286

259287
class IPv4Address(BaseDomain):

tests/unit/firewalls/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)