replace predefined ssh keys with generated ones

This commit is contained in:
Franzi 2023-03-31 21:41:12 +02:00
parent 8d3e913a8c
commit 28298d3ce6
Signed by: kunsi
GPG key ID: 12E3D2136B818350
3 changed files with 105 additions and 6 deletions

View file

@ -33,14 +33,17 @@ else:
backup_target = repo.get_node(node.metadata.get('backup-client/target')) backup_target = repo.get_node(node.metadata.get('backup-client/target'))
files['/etc/backup.priv'] = { files['/etc/backup.priv'] = {
'content': repo.vault.decrypt_file(join('backup', 'keys', f'{node.name}.key.vault')), 'content': repo.libs.ssh.generate_ed25519_private_key(
node.metadata.get('backup-client/user-name'),
backup_target,
),
'mode': '0400', 'mode': '0400',
} }
files['/usr/local/bin/generate-backup'] = { files['/usr/local/bin/generate-backup'] = {
'content_type': 'mako', 'content_type': 'mako',
'context': { 'context': {
'username': node.metadata['backup-client']['user-name'], 'username': node.metadata.get('backup-client/user-name'),
'server': backup_target.metadata.get('backup-server/my_hostname'), 'server': backup_target.metadata.get('backup-server/my_hostname'),
'port': backup_target.metadata.get('backup-server/my_ssh_port'), 'port': backup_target.metadata.get('backup-server/my_ssh_port'),
'paths': backup_paths, 'paths': backup_paths,

View file

@ -27,9 +27,6 @@ directories['/etc/backup-server/clients'] = {
sudoers = {} sudoers = {}
for nodename, config in node.metadata.get('backup-server/clients', {}).items(): for nodename, config in node.metadata.get('backup-server/clients', {}).items():
with open(join(repo.path, 'data', 'backup', 'keys', f'{nodename}.pub'), 'r') as f:
pubkey = f.read().strip()
sudoers[config['user']] = nodename sudoers[config['user']] = nodename
users[config['user']] = { users[config['user']] = {
@ -41,7 +38,10 @@ for nodename, config in node.metadata.get('backup-server/clients', {}).items():
} }
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = { files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
'content': pubkey, 'content': repo.libs.ssh.generate_ed25519_public_key(
config['user'],
node,
),
'owner': config['user'], 'owner': config['user'],
'mode': '0400', 'mode': '0400',
'needs': { 'needs': {

96
libs/ssh.py Normal file
View file

@ -0,0 +1,96 @@
from base64 import b64decode, b64encode
from functools import lru_cache
from hashlib import sha3_224
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import (
Encoding,
NoEncryption,
PrivateFormat,
PublicFormat,
)
from bundlewrap.utils import Fault
@lru_cache(maxsize=None)
def generate_ed25519_private_key(username, node):
return Fault(
f'private key {username}@{node.name}',
lambda username, node: _generate_ed25519_private_key(username, node),
username=username,
node=node,
)
@lru_cache(maxsize=None)
def generate_ed25519_public_key(username, node):
return Fault(
f'public key {username}@{node.name}',
lambda username, node: _generate_ed25519_public_key(username, node),
username=username,
node=node,
)
def _generate_ed25519_private_key(username, node):
privkey_bytes = Ed25519PrivateKey.from_private_bytes(_secret(username, node))
nondeterministic_privatekey = privkey_bytes.private_bytes(
encoding=Encoding.PEM,
format=PrivateFormat.OpenSSH,
encryption_algorithm=NoEncryption(),
).decode()
# get relevant lines from string
nondeterministic_bytes = b64decode(
''.join(nondeterministic_privatekey.split('\n')[1:-2])
)
# sanity check
if nondeterministic_bytes[98:102] != nondeterministic_bytes[102:106]:
raise Exception("checksums should be the same: whats going on here?")
# replace random bytes with deterministic values
random_bytes = sha3_224(_secret(username, node)).digest()[0:4]
deterministic_bytes = (
nondeterministic_bytes[:98]
+ random_bytes
+ random_bytes
+ nondeterministic_bytes[106:]
)
# reassemble file
deterministic_privatekey = '\n'.join(
[
'-----BEGIN OPENSSH PRIVATE KEY-----',
b64encode(deterministic_bytes).decode(),
'-----END OPENSSH PRIVATE KEY-----',
]
) + '\n'
return deterministic_privatekey
def _generate_ed25519_public_key(username, node):
return (
Ed25519PrivateKey.from_private_bytes(_secret(username, node))
.public_key()
.public_bytes(
encoding=Encoding.OpenSSH,
format=PublicFormat.OpenSSH,
)
.decode()
+ f' {username}@{node.name}'
)
@lru_cache(maxsize=None)
def _secret(username, node):
return b64decode(
str(
node.repo.vault.random_bytes_as_base64_for(
f"{username}@{node.name}", length=32
)
)
)