Skip to content

Commit 9c4babf

Browse files
committed
feat(cert-manager): add tls events
1 parent 6f5f405 commit 9c4babf

13 files changed

Lines changed: 395 additions & 105 deletions

File tree

charts/controller/templates/controller-clusterrole.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ rules:
5959
resources: ["ingresses"]
6060
verbs: ["get", "list", "watch", "create", "update", "delete"]
6161
- apiGroups: ["cert-manager.io"]
62-
resources: ["certificates", "issuers"]
62+
resources: ["certificates", "certificaterequest", "issuers"]
6363
verbs: ["get", "list", "watch", "create", "update", "delete"]
6464
- apiGroups: ["networking.k8s.io"]
6565
resources: ["ingresses"]

rootfs/api/models/app.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from api.utils import generate_app_name, apply_tasks
2626
from scheduler import KubeHTTPException, KubeException
2727
from scheduler.resources.pod import DEFAULT_CONTAINER_PORT
28-
from .gateway import Gateway, Route, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT
28+
from .gateway import Gateway, Route, DEFAULT_HTTP_PORT
2929
from .limit import LimitPlan
3030
from .config import Config
3131
from .service import Service
@@ -767,17 +767,19 @@ def _create_default_ingress(self, target_port):
767767
# create default gateway
768768
try:
769769
gateway = self.gateway_set.filter(name=self.id).latest()
770+
if gateway.change_default_tls():
771+
gateway.save()
770772
except Gateway.DoesNotExist:
771773
gateway = Gateway(app=self, owner=self.owner, name=self.id)
772-
modified = gateway.add(DEFAULT_HTTP_PORT, "HTTP")
773-
if self.tls_set.latest().certs_auto_enabled or self.domain_set.filter(
774-
models.Q(certificate__isnull=False)).exists():
775-
modified = gateway.add(DEFAULT_HTTPS_PORT, "HTTPS") if not modified else True
776-
if modified:
774+
added, msg = gateway.add(DEFAULT_HTTP_PORT, "HTTP")
775+
if not added:
776+
raise DryccException(msg)
777777
gateway.save()
778778
# create default route
779779
try:
780-
self.route_set.filter(name=self.id).latest()
780+
route = self.route_set.filter(name=self.id).latest()
781+
if route.change_default_tls():
782+
route.save()
781783
except Route.DoesNotExist:
782784
route = Route(app=self, owner=self.owner, kind="HTTPRoute", name=self.id,
783785
port=DEFAULT_HTTP_PORT, procfile_type=service.procfile_type)

rootfs/api/models/gateway.py

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,6 @@ class Gateway(AuditedModel):
2222
name = models.CharField(max_length=63, db_index=True)
2323
ports = models.JSONField(default=list)
2424

25-
def _check_port(self, port, protocol):
26-
for item in self.ports:
27-
if item["port"] == port:
28-
if (item["protocol"] == protocol) or (
29-
item["protocol"] != "UDP" and protocol != "UDP"):
30-
return False
31-
return True
32-
33-
def _get_tls_domain(self, auto_tls):
34-
domains = self.app.domain_set.all()
35-
if not auto_tls:
36-
domains = domains.exclude(certificate=None)
37-
return domains
38-
39-
def _get_listener_name(self, port, protocol, index):
40-
if protocol in ("TCP", "TLS", "HTTP"):
41-
protocol = "TCP"
42-
elif protocol in ("HTTPS", ):
43-
protocol = "MIX"
44-
else:
45-
protocol = "UDP"
46-
return "-".join([protocol, str(port), str(index)]).lower()
47-
4825
def add(self, port, protocol):
4926
# check port
5027
if not self._check_port(port, protocol):
@@ -125,7 +102,19 @@ def refresh_to_k8s(self):
125102
except KubeException as e:
126103
raise ServiceUnavailable('Kubernetes gateway could not be created') from e
127104

105+
def change_default_tls(self):
106+
if self.name != self.app.id:
107+
return False
108+
tls_enabled = (self.app.tls_set.latest().certs_auto_enabled and
109+
self.app.domain_set.exists()) or self.app.domain_set.filter(
110+
models.Q(certificate__isnull=False)).exists()
111+
if tls_enabled:
112+
return self.add(DEFAULT_HTTPS_PORT, "HTTPS")[0]
113+
else:
114+
return self.remove(DEFAULT_HTTPS_PORT, "HTTPS")[0]
115+
128116
def save(self, *args, **kwargs):
117+
self.change_default_tls()
129118
super().save(*args, **kwargs)
130119
self.refresh_to_k8s()
131120

@@ -139,6 +128,29 @@ def delete(self, *args, **kwargs):
139128
)
140129
return super().delete(*args, **kwargs)
141130

131+
def _check_port(self, port, protocol):
132+
for item in self.ports:
133+
if item["port"] == port:
134+
if (item["protocol"] == protocol) or (
135+
item["protocol"] != "UDP" and protocol != "UDP"):
136+
return False
137+
return True
138+
139+
def _get_tls_domain(self, auto_tls):
140+
domains = self.app.domain_set.all()
141+
if not auto_tls:
142+
domains = domains.exclude(certificate=None)
143+
return domains
144+
145+
def _get_listener_name(self, port, protocol, index):
146+
if protocol in ("TCP", "TLS", "HTTP"):
147+
protocol = "TCP"
148+
elif protocol in ("HTTPS", ):
149+
protocol = "MIX"
150+
else:
151+
protocol = "UDP"
152+
return "-".join([protocol, str(port), str(index)]).lower()
153+
142154
class Meta:
143155
get_latest_by = 'created'
144156
unique_together = (('app', 'name'), )
@@ -233,6 +245,17 @@ def refresh_to_k8s(self):
233245
self.scheduler().httproute.delete(self.app.id, self.name)
234246
self.scheduler().httproute.delete(self.app.id, self._https_redirect_name)
235247

248+
def change_default_tls(self):
249+
if self.app.id != self.name:
250+
return False
251+
tls_enabled = (self.app.tls_set.latest().certs_auto_enabled and
252+
self.app.domain_set.exists()) or self.app.domain_set.filter(
253+
models.Q(certificate__isnull=False)).exists()
254+
if tls_enabled:
255+
return self.attach(self.app.id, DEFAULT_HTTPS_PORT)[0]
256+
else:
257+
return self.detach(self.app.id, DEFAULT_HTTPS_PORT)[0]
258+
236259
def attach(self, gateway_name, port):
237260
ok, msg = self._check_parent(gateway_name, port)
238261
if not ok:
@@ -253,6 +276,7 @@ def detach(self, gateway_name, port):
253276
return True, ""
254277

255278
def save(self, *args, **kwargs):
279+
self.change_default_tls()
256280
ok, msg = self.check_rules()
257281
if not ok:
258282
raise ValueError(msg)

rootfs/api/models/tls.py

Lines changed: 77 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,51 +29,6 @@ class TLS(UuidAuditedModel):
2929
https_enforced = models.BooleanField(null=True)
3030
certs_auto_enabled = models.BooleanField(null=True)
3131

32-
class Meta:
33-
get_latest_by = 'created'
34-
unique_together = (('app', 'uuid'))
35-
ordering = ['-created']
36-
37-
def __str__(self):
38-
return "{}-{}".format(self.app.id, str(self.uuid)[:7])
39-
40-
def _check_previous_tls_settings(self):
41-
"""
42-
Only one value can be set at a time
43-
If the other value is None, using the previous setting.
44-
"""
45-
try:
46-
previous_tls_settings = self.app.tls_set.latest()
47-
if self.https_enforced is not None:
48-
if previous_tls_settings.https_enforced == self.https_enforced:
49-
raise AlreadyExists(
50-
"{} changed nothing".format(self.owner))
51-
self.certs_auto_enabled = previous_tls_settings.certs_auto_enabled
52-
elif self.certs_auto_enabled is not None:
53-
if previous_tls_settings.certs_auto_enabled == self.certs_auto_enabled:
54-
raise AlreadyExists(
55-
"{} changed nothing".format(self.owner))
56-
self.https_enforced = previous_tls_settings.https_enforced
57-
previous_tls_settings.delete()
58-
except TLS.DoesNotExist:
59-
pass
60-
61-
def _refresh_secret_to_k8s(self):
62-
secret_name = f"{self.app.id}-acme-external-account-binding-secret"
63-
try:
64-
try:
65-
data = self.scheduler().secret.get(self.app.id, secret_name).json()
66-
self.scheduler().secret.patch(self.app.id, secret_name, {
67-
"secret": self.issuer["key_secret"],
68-
"version": data["metadata"]["resourceVersion"],
69-
})
70-
except KubeException:
71-
self.scheduler().secret.create(self.app.id, secret_name, {
72-
"secret": self.issuer["key_secret"],
73-
})
74-
except KubeException as e:
75-
raise ServiceUnavailable('Kubernetes secret could not be created') from e
76-
7732
def log(self, message, level=logging.INFO):
7833
"""Logs a message in the context of this application.
7934
@@ -85,6 +40,37 @@ def log(self, message, level=logging.INFO):
8540
"""
8641
logger.log(level, "[{}]: {}".format(self.app.id, message))
8742

43+
@property
44+
def events(self):
45+
def to_result(name, kind, condition):
46+
return {
47+
"name": name,
48+
"kind": kind,
49+
"time": condition["lastTransitionTime"],
50+
"type": condition["type"],
51+
"status": condition["status"],
52+
"message": condition["message"],
53+
}
54+
55+
results = []
56+
name = namespace = self.app.id
57+
response = self.scheduler().issuer.get(namespace, name, ignore_exception=True)
58+
if response.status_code == 200:
59+
for condition in response.json()["status"]["conditions"]:
60+
results.append(to_result(name, "Issuer", condition))
61+
name = f"{self.app.id}-auto-tls"
62+
response = self.scheduler().certificate.get(namespace, name, ignore_exception=True)
63+
if response.status_code == 200:
64+
for condition in response.json()["status"]["conditions"]:
65+
results.append(to_result(name, "Certificate", condition))
66+
response = self.scheduler().certificaterequest.get(namespace, ignore_exception=True)
67+
if response.status_code == 200:
68+
for item in response.json()["items"]:
69+
for condition in item["status"]["conditions"]:
70+
results.append(to_result(
71+
item["metadata"]["name"], "CertificateRequest", condition))
72+
return results
73+
8874
def refresh_issuer_to_k8s(self):
8975
name = namespace = self.app.id
9076
try:
@@ -110,7 +96,7 @@ def refresh_issuer_to_k8s(self):
11096
raise ServiceUnavailable('Kubernetes issuer could not be created') from e
11197

11298
def refresh_certificate_to_k8s(self):
113-
namespace = name = self.app.id
99+
namespace, name = self.app.id, f"{self.app.id}-auto-tls"
114100
if self.certs_auto_enabled:
115101
hosts = [domain.domain for domain in self.app.domain_set.all()]
116102
if len(hosts) > 0:
@@ -132,3 +118,48 @@ def refresh_certificate_to_k8s(self):
132118
def save(self, *args, **kwargs):
133119
self._check_previous_tls_settings()
134120
super(TLS, self).save(*args, **kwargs)
121+
122+
def __str__(self):
123+
return "{}-{}".format(self.app.id, str(self.uuid)[:7])
124+
125+
def _check_previous_tls_settings(self):
126+
"""
127+
Only one value can be set at a time
128+
If the other value is None, using the previous setting.
129+
"""
130+
try:
131+
previous_tls_settings = self.app.tls_set.latest()
132+
if self.https_enforced is not None:
133+
if previous_tls_settings.https_enforced == self.https_enforced:
134+
raise AlreadyExists(
135+
"{} changed nothing".format(self.owner))
136+
self.certs_auto_enabled = previous_tls_settings.certs_auto_enabled
137+
elif self.certs_auto_enabled is not None:
138+
if previous_tls_settings.certs_auto_enabled == self.certs_auto_enabled:
139+
raise AlreadyExists(
140+
"{} changed nothing".format(self.owner))
141+
self.https_enforced = previous_tls_settings.https_enforced
142+
previous_tls_settings.delete()
143+
except TLS.DoesNotExist:
144+
pass
145+
146+
def _refresh_secret_to_k8s(self):
147+
secret_name = f"{self.app.id}-acme-external-account-binding-secret"
148+
try:
149+
try:
150+
data = self.scheduler().secret.get(self.app.id, secret_name).json()
151+
self.scheduler().secret.patch(self.app.id, secret_name, {
152+
"secret": self.issuer["key_secret"],
153+
"version": data["metadata"]["resourceVersion"],
154+
})
155+
except KubeException:
156+
self.scheduler().secret.create(self.app.id, secret_name, {
157+
"secret": self.issuer["key_secret"],
158+
})
159+
except KubeException as e:
160+
raise ServiceUnavailable('Kubernetes secret could not be created') from e
161+
162+
class Meta:
163+
get_latest_by = 'created'
164+
unique_together = (('app', 'uuid'))
165+
ordering = ['-created']

rootfs/api/serializers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ class TLSSerializer(serializers.ModelSerializer):
535535

536536
app = serializers.SlugRelatedField(slug_field='id', queryset=models.app.App.objects.all())
537537
owner = serializers.ReadOnlyField(source='owner.username')
538+
events = serializers.ReadOnlyField()
538539

539540
class Meta:
540541
"""Metadata options for a :class:`AppTLSSerializer`."""

rootfs/api/signals.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from api.tasks import send_measurements
1919
from api.models.app import App
2020
from api.models.service import Service
21-
from api.models.gateway import Gateway, DEFAULT_HTTPS_PORT
21+
from api.models.gateway import Gateway
2222
from api.models.appsettings import AppSettings
2323
from api.models.build import Build
2424
from api.models.certificate import Certificate
@@ -165,13 +165,15 @@ def tls_changed_handle(sender, instance: TLS, created=False, update_fields=None,
165165
if (update_fields and "certs_auto_enabled" in update_fields) or created:
166166
instance.refresh_certificate_to_k8s()
167167
for gateway in instance.app.gateway_set.all():
168-
if (instance.certs_auto_enabled and gateway.name == instance.app.id
169-
and gateway.add(DEFAULT_HTTPS_PORT, "HTTPS")[0]):
168+
if gateway.change_default_tls():
170169
gateway.save()
171170
else:
172171
gateway.refresh_to_k8s()
173172
for route in instance.app.route_set.all():
174-
route.refresh_to_k8s()
173+
if route.change_default_tls():
174+
route.save()
175+
else:
176+
route.refresh_to_k8s()
175177

176178

177179
@receiver(post_save, sender=Gateway)
@@ -195,9 +197,15 @@ def service_changed_handle(
195197
def domain_changed_handle(
196198
sender, instance: Domain, created=False, update_fields=None, **kwargs):
197199
for gateway in instance.app.gateway_set.all():
198-
gateway.refresh_to_k8s()
200+
if gateway.change_default_tls():
201+
gateway.save()
202+
else:
203+
gateway.refresh_to_k8s()
199204
for route in instance.app.route_set.all():
200-
route.refresh_to_k8s()
205+
if route.change_default_tls():
206+
route.save()
207+
else:
208+
route.refresh_to_k8s()
201209

202210

203211
@receiver(signal=[post_save, post_delete], sender=AppSettings)

rootfs/api/tests/test_config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,13 +486,15 @@ def test_unset_limits_error(self, mock_requests):
486486
sha='somereallylongsha'
487487
)
488488
# create an initial release
489-
Release.objects.create(
489+
release = Release.objects.create(
490490
version=3,
491491
owner=user,
492492
app=app,
493493
config=app.config_set.latest(),
494494
build=build
495495
)
496+
# deploy
497+
app.pipeline(release)
496498
# unset error
497499
body = {
498500
'limits': {

0 commit comments

Comments
 (0)