Skip to content

Commit 129c31b

Browse files
committed
Implementation of SSH keys for Secret Manager
SSH keys are an integral part of the Secret Manager service, which allows users to manage and deliver them to nodes. Examples: ```bash curl --location 'http://10.20.0.2:11010/v1/secret/ssh_keys/' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer MY_TOKEN' \ --data-raw '{ "name": "my-key", "project_id": "00000000-0000-0000-0000-000000000000", "user": "ubuntu", "target": { "kind": "node", "node": "10000000-1000-1000-1000-000000000001" }, "target_public_key": "ssh-rsa AAAABBBBCCCC user@user-pc" }' ``` When the key is delivered to the target, it is added to the authorized keys ($HOME/.ssh/authorized_keys) of the user on the target. Also it's status is set to `ACTIVE`. Docs: https://github.com/infraguys/genesis_core/wiki/SSHKeys Signed-off-by: Anton Kremenetsky <anton.kremenetsky@gmail.com>
1 parent 5b9bd4c commit 129c31b

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)