Skip to content

Commit 2c0cda5

Browse files
committed
SSH keys implementation
Signed-off-by: Anton Kremenetsky <anton.kremenetsky@gmail.com>
1 parent 5b9bd4c commit 2c0cda5

10 files changed

Lines changed: 1072 additions & 1 deletion

File tree

genesis_core/config/service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ def _get_outdated_configs(
8484
order_by={"uuid": "asc"},
8585
)
8686
resources = ua_models.TargetResource.objects.get_all(
87-
filters={"uuid": dm_filters.In(str(cfg) for cfg in config_uuids)},
87+
filters={
88+
"uuid": dm_filters.In(str(cfg) for cfg in config_uuids),
89+
"kind": dm_filters.EQ(cc.CONFIG_KIND),
90+
},
8891
order_by={"uuid": "asc"},
8992
)
9093

genesis_core/secret/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
DEFAULT_SQL_LIMIT = 100
2020
PASSWORD_KIND = "password"
2121
CERTIFICATE_KIND = "certificate"
22+
SSH_KEY_KIND = "ssh_key"
23+
SSH_KEY_TARGET_KIND = "ssh_key_target"
24+
25+
AUTHORIZED_KEYS_PATH = ".ssh/authorized_keys"
2226

2327

2428
class SecretStatus(str, enum.Enum):

genesis_core/secret/dm/models.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
# under the License.
1616
from __future__ import annotations
1717

18+
import typing as tp
19+
import uuid as sys_uuid
20+
1821
from restalchemy.dm import properties
1922
from restalchemy.dm import types
2023
from restalchemy.dm import types_network
@@ -25,6 +28,7 @@
2528
from gcl_sdk.agents.universal.dm import models as ua_models
2629

2730
from genesis_core.common.dm import models as cm
31+
from genesis_core.config.dm import models as cfg_models
2832
from genesis_core.secret import constants as sc
2933

3034

@@ -206,3 +210,126 @@ def get_deleted_certificates(
206210
return cls.get_deleted_target_resources(
207211
cls.__tablename__, sc.CERTIFICATE_KIND, limit
208212
)
213+
214+
215+
class SSHKey(
216+
Secret,
217+
orm.SQLStorableMixin,
218+
ua_models.TargetResourceSQLStorableMixin,
219+
):
220+
__tablename__ = "secret_ssh_keys"
221+
222+
target = properties.property(
223+
types_dynamic.KindModelSelectorType(
224+
types_dynamic.KindModelType(cfg_models.NodeTarget),
225+
),
226+
required=True,
227+
)
228+
user = properties.property(types.String(min_length=1, max_length=64))
229+
authorized_keys = properties.property(
230+
types.String(min_length=1, max_length=256),
231+
default=sc.AUTHORIZED_KEYS_PATH,
232+
)
233+
target_public_key = properties.property(
234+
types.String(max_length=10240),
235+
default="",
236+
)
237+
238+
def target_nodes(self) -> tp.List[sys_uuid.UUID]:
239+
return self.target.target_nodes()
240+
241+
def get_resource_target_fields(self) -> set[str]:
242+
"""Return the collection of target fields.
243+
244+
Refer to the Resource model for more details about target fields.
245+
"""
246+
return {
247+
"uuid",
248+
"name",
249+
"description",
250+
"project_id",
251+
"constructor",
252+
"user",
253+
"authorized_keys",
254+
"target",
255+
"target_public_key",
256+
}
257+
258+
def to_host_resource(
259+
self,
260+
master: sys_uuid.UUID,
261+
node: sys_uuid.UUID,
262+
status: sc.SecretStatus | None = None,
263+
) -> ua_models.TargetResource:
264+
"""Create a target resource for a specific host (node).
265+
266+
This creates a 'slave' resource for a specific node, which is linked
267+
to the 'master' SSHKey secret.
268+
269+
Args:
270+
master: The UUID of the master SSHKey secret.
271+
node: The UUID of the target node.
272+
status: The initial status for the host resource.
273+
274+
Returns:
275+
A TargetResource instance for the host.
276+
"""
277+
properties = {}
278+
279+
# Copy properties
280+
for name in self.properties.properties.keys():
281+
if name not in SSHHostKey.properties.properties:
282+
continue
283+
properties[name] = getattr(self, name)
284+
285+
# Correct UUID based on node UUID
286+
properties["uuid"] = sys_uuid.uuid5(self.uuid, str(node))
287+
host_ssh = SSHHostKey(**properties)
288+
289+
resource = host_ssh.to_ua_resource(
290+
sc.SSH_KEY_TARGET_KIND, master=master
291+
)
292+
if status is not None:
293+
resource.status = status.value
294+
# Place the key on the node
295+
resource.agent = node
296+
297+
return resource
298+
299+
@classmethod
300+
def get_new_keys(cls, limit: int = sc.DEFAULT_SQL_LIMIT) -> list["SSHKey"]:
301+
302+
return cls.get_new_entities(cls.__tablename__, sc.SSH_KEY_KIND, limit)
303+
304+
@classmethod
305+
def get_updated_keys(
306+
cls, limit: int = sc.DEFAULT_SQL_LIMIT
307+
) -> list["SSHKey"]:
308+
return cls.get_updated_entities(
309+
cls.__tablename__, sc.SSH_KEY_KIND, limit
310+
)
311+
312+
@classmethod
313+
def get_deleted_keys(
314+
cls, limit: int = sc.DEFAULT_SQL_LIMIT
315+
) -> list[ua_models.TargetResource]:
316+
return cls.get_deleted_target_resources(
317+
cls.__tablename__, sc.SSH_KEY_KIND, limit
318+
)
319+
320+
321+
class SSHHostKey(
322+
ra_models.ModelWithUUID,
323+
ua_models.TargetResourceMixin,
324+
):
325+
"""SSH host key model."""
326+
327+
user = properties.property(types.String(min_length=1, max_length=64))
328+
authorized_keys = properties.property(
329+
types.String(min_length=1, max_length=256),
330+
default=sc.AUTHORIZED_KEYS_PATH,
331+
)
332+
target_public_key = properties.property(
333+
types.String(max_length=10240),
334+
default="",
335+
)

0 commit comments

Comments
 (0)