Skip to content

Commit 41b9baa

Browse files
committed
Certificate implementation
Signed-off-by: Anton Kremenetsky <anton.kremenetsky@gmail.com>
1 parent 8e5b2a6 commit 41b9baa

14 files changed

Lines changed: 1205 additions & 126 deletions

File tree

etc/genesis_universal_agent/genesis_universal_agent.conf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ connection_pool_size = 2
1111
[universal_agent]
1212
orch_endpoint = http://localhost:11011
1313
status_endpoint = http://localhost:11012
14-
caps_drivers = CoreCapabilityDriver,PasswordCapabilityDriver
14+
caps_drivers = CoreCapabilityDriver,PasswordCapabilityDriver,CoreDNSCertificateCapabilityDriver
1515

1616

1717
[universal_agent_scheduler]
18-
capabilities = em_core_*,password
18+
capabilities = em_core_*,password,certificate
1919

2020

2121
[CoreCapabilityDriver]
@@ -26,3 +26,4 @@ project_id = 12345678-c625-4fee-81d5-f691897b8142
2626
em_core_compute_nodes = /v1/nodes/
2727
em_core_config_configs = /v1/config/configs/
2828
em_core_secret_passwords = /v1/secret/passwords/
29+
em_core_secret_certificates = /v1/secret/certificates/
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Copyright 2025 Genesis Corporation.
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
from __future__ import annotations
17+
18+
import logging
19+
import typing as tp
20+
21+
from gcl_sdk.agents.universal.dm import models
22+
from gcl_sdk.agents.universal.clients.backend import base
23+
from gcl_sdk.agents.universal.clients.backend import exceptions
24+
25+
26+
LOG = logging.getLogger(__name__)
27+
28+
29+
class CertBotBackendClient(base.AbstractBackendClient):
30+
"""Cert bot backend client."""
31+
32+
def __init__(self):
33+
self._certs = []
34+
35+
def get(self, resource: models.Resource) -> dict[str, tp.Any]:
36+
"""Get the resource value in dictionary format."""
37+
for cert in self._certs:
38+
if cert["uuid"] == str(resource.uuid):
39+
return cert
40+
41+
raise exceptions.ResourceNotFound(resource=resource)
42+
43+
def create(self, resource: models.Resource) -> dict[str, tp.Any]:
44+
"""Creates the resource. Returns the created resource."""
45+
try:
46+
self.get(resource)
47+
except exceptions.ResourceNotFound:
48+
pass
49+
else:
50+
raise exceptions.ResourceAlreadyExists(resource=resource)
51+
52+
value = resource.value.copy()
53+
value["status"] = "ACTIVE"
54+
value["key"] = "PRIVATE KEY"
55+
value["cert"] = "CERTIFICATE"
56+
57+
self._certs.append(value)
58+
return value
59+
60+
def update(self, resource: models.Resource) -> dict[str, tp.Any]:
61+
"""Update the resource. Returns the updated resource."""
62+
63+
# The simplest implementation. Update via recreation.
64+
self.delete(resource)
65+
return self.create(resource)
66+
67+
def list(self, kind: str, **kwargs) -> list[dict[str, tp.Any]]:
68+
"""Lists all resources by kind."""
69+
return self._certs
70+
71+
def delete(self, resource: models.Resource) -> None:
72+
"""Delete the resource."""
73+
try:
74+
self.get(resource)
75+
except exceptions.ResourceNotFound:
76+
raise exceptions.ResourceNotFound(resource=resource)
77+
78+
for i, cert in enumerate(self._certs):
79+
if cert["uuid"] == str(resource.uuid):
80+
break
81+
else:
82+
raise exceptions.ResourceNotFound(resource=resource)
83+
84+
self._certs.pop(i)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2025 Genesis Corporation.
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
from __future__ import annotations
17+
18+
import logging
19+
20+
from gcl_sdk.agents.universal.drivers import direct
21+
from gcl_sdk.agents.universal.storage import fs
22+
23+
from genesis_core.agent.universal.drivers.secret.backend import (
24+
cert as cert_back,
25+
)
26+
27+
28+
LOG = logging.getLogger(__name__)
29+
30+
AGENT_WORK_DIR = "/var/lib/genesis/universal_agent/"
31+
32+
33+
class CoreDNSCertificateCapabilityDriver(direct.DirectAgentDriver):
34+
"""Certificate capability driver."""
35+
36+
def __init__(self):
37+
storage = fs.FileAgentStorage(AGENT_WORK_DIR, "cert_cap_storage.json")
38+
client = cert_back.CertBotBackendClient()
39+
40+
super().__init__(storage=storage, client=client)
41+
42+
def get_capabilities(self) -> list[str]:
43+
"""Returns a list of capabilities supported by the driver."""
44+
return ["certificate"]

genesis_core/secret/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
DEFAULT_SQL_LIMIT = 100
2020
PASSWORD_KIND = "password"
21+
CERTIFICATE_KIND = "certificate"
2122

2223

2324
class SecretStatus(str, enum.Enum):

genesis_core/secret/dm/models.py

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,48 @@
2727
from genesis_core.secret import constants as sc
2828

2929

30-
class AbstractPasswordConstructor(
30+
class AbstractSecretConstructor(
3131
types_dynamic.AbstractKindModel, ra_models.SimpleViewMixin
3232
):
3333

34-
def build(self, plain_password: str) -> str:
34+
def build(self, plain_secret: str) -> str:
3535
raise NotImplementedError()
3636

3737

38-
class PlainPasswordConstructor(AbstractPasswordConstructor):
38+
class PlainSecretConstructor(AbstractSecretConstructor):
3939
KIND = "plain"
4040

41-
def build(self, plain_password: str) -> str:
42-
return plain_password
41+
def build(self, plain_secret: str) -> str:
42+
return plain_secret
4343

4444

45-
class Password(
45+
class Secret(
4646
cm.ModelWithFullAsset,
47-
orm.SQLStorableMixin,
4847
ua_models.TargetResourceMixin,
49-
ua_models.TargetResourceSQLStorableMixin,
5048
):
51-
__tablename__ = "secret_passwords"
52-
53-
method = properties.property(
54-
types.Enum([s.value for s in sc.SecretMethod]),
55-
default=sc.SecretMethod.AUTO_HEX.value,
56-
)
5749
status = properties.property(
5850
types.Enum([s.value for s in sc.SecretStatus]),
5951
default=sc.SecretStatus.NEW.value,
6052
)
6153
constructor = properties.property(
6254
types_dynamic.KindModelSelectorType(
63-
types_dynamic.KindModelType(PlainPasswordConstructor),
55+
types_dynamic.KindModelType(PlainSecretConstructor),
6456
),
6557
required=True,
66-
default=PlainPasswordConstructor,
58+
default=PlainSecretConstructor,
59+
)
60+
61+
62+
class Password(
63+
Secret,
64+
orm.SQLStorableMixin,
65+
ua_models.TargetResourceSQLStorableMixin,
66+
):
67+
__tablename__ = "secret_passwords"
68+
69+
method = properties.property(
70+
types.Enum([s.value for s in sc.SecretMethod]),
71+
default=sc.SecretMethod.AUTO_HEX.value,
6772
)
6873
value = properties.property(
6974
types.AllowNone(types.String(min_length=1, max_length=512)),
@@ -105,3 +110,87 @@ def get_deleted_passwords(
105110
return cls.get_deleted_target_resources(
106111
cls.__tablename__, sc.PASSWORD_KIND, limit
107112
)
113+
114+
115+
class AbstractCertificateMethod(
116+
types_dynamic.AbstractKindModel, ra_models.SimpleViewMixin
117+
):
118+
pass
119+
120+
121+
class DNSCoreCertificateMethod(AbstractCertificateMethod):
122+
KIND = "dns_core"
123+
124+
125+
class Certificate(
126+
Secret,
127+
orm.SQLStorableWithJSONFieldsMixin,
128+
ua_models.TargetResourceSQLStorableMixin,
129+
):
130+
__tablename__ = "secret_certificates"
131+
__jsonfields__ = ["domains"]
132+
133+
method = properties.property(
134+
types_dynamic.KindModelSelectorType(
135+
types_dynamic.KindModelType(DNSCoreCertificateMethod),
136+
),
137+
required=True,
138+
default=DNSCoreCertificateMethod,
139+
)
140+
valid_until = properties.property(
141+
types.AllowNone(types.UTCDateTimeZ()),
142+
default=None,
143+
)
144+
email = properties.property(types.Email())
145+
domains = properties.property(
146+
types.TypedList(types.String()),
147+
required=True,
148+
)
149+
key = properties.property(
150+
types.AllowNone(types.String(min_length=1, max_length=10240)),
151+
default=None,
152+
)
153+
cert = properties.property(
154+
types.AllowNone(types.String(min_length=1, max_length=10240)),
155+
default=None,
156+
)
157+
158+
def get_resource_target_fields(self) -> set[str]:
159+
"""Return the collection of target fields.
160+
161+
Refer to the Resource model for more details about target fields.
162+
"""
163+
return {
164+
"method",
165+
"email",
166+
"domains",
167+
"constructor",
168+
"name",
169+
"description",
170+
"project_id",
171+
"uuid",
172+
}
173+
174+
@classmethod
175+
def get_new_certificates(
176+
cls, limit: int = sc.DEFAULT_SQL_LIMIT
177+
) -> list["Certificate"]:
178+
return cls.get_new_entities(
179+
cls.__tablename__, sc.CERTIFICATE_KIND, limit
180+
)
181+
182+
@classmethod
183+
def get_updated_certificates(
184+
cls, limit: int = sc.DEFAULT_SQL_LIMIT
185+
) -> list["Certificate"]:
186+
return cls.get_updated_entities(
187+
cls.__tablename__, sc.CERTIFICATE_KIND, limit
188+
)
189+
190+
@classmethod
191+
def get_deleted_certificates(
192+
cls, limit: int = sc.DEFAULT_SQL_LIMIT
193+
) -> list[ua_models.TargetResource]:
194+
return cls.get_deleted_target_resources(
195+
cls.__tablename__, sc.CERTIFICATE_KIND, limit
196+
)

0 commit comments

Comments
 (0)