Compare commits
	
		
			5 commits
		
	
	
		
			5ab169efe0
			...
			8a9434a384
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							 | 
						8a9434a384 | ||
| 
							 | 
						24bf39dda5 | ||
| 
							 | 
						0dbda1c200 | ||
| 
							 | 
						dab554473e | ||
| 
							 | 
						8b3f9d7736 | 
					 12 changed files with 317 additions and 29 deletions
				
			
		| 
						 | 
					@ -7,7 +7,7 @@ then
 | 
				
			||||||
  /opt/backup/backup_path_via_zfs "$path"
 | 
					  /opt/backup/backup_path_via_zfs "$path"
 | 
				
			||||||
elif test -d "$path"
 | 
					elif test -d "$path"
 | 
				
			||||||
then
 | 
					then
 | 
				
			||||||
  /opt/backuo/backup_path_via_rsync "$path"
 | 
					  /opt/backup/backup_path_via_rsync "$path"
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
  echo "UNKNOWN PATH: $path"
 | 
					  echo "UNKNOWN PATH: $path"
 | 
				
			||||||
  exit 1
 | 
					  exit 1
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ set -exu
 | 
				
			||||||
path=$1
 | 
					path=$1
 | 
				
			||||||
uuid=$(jq -r .client_uuid < /etc/backup/config.json)
 | 
					uuid=$(jq -r .client_uuid < /etc/backup/config.json)
 | 
				
			||||||
server=$(jq -r .server_hostname < /etc/backup/config.json)
 | 
					server=$(jq -r .server_hostname < /etc/backup/config.json)
 | 
				
			||||||
ssh="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 backup-receiver@$server"
 | 
					ssh="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 backup-receiver@$server"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rsync -av --rsync-path="sudo rsync" "$path/" "backup-receiver@$server:/mnt/backups/$uuid$path/"
 | 
					rsync -av --rsync-path="sudo rsync" "$path/" "backup-receiver@$server:/mnt/backups/$uuid$path/"
 | 
				
			||||||
$ssh sudo zfs snap "tank/$uuid/fs@auto-backup_$(date +"%Y-%m-%d_%H:%M:%S")" 
 | 
					$ssh sudo zfs snap "tank/$uuid/fs@auto-backup_$(date +"%Y-%m-%d_%H:%M:%S")" 
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ set -exu
 | 
				
			||||||
path=$1
 | 
					path=$1
 | 
				
			||||||
uuid=$(jq -r .client_uuid < /etc/backup/config.json)
 | 
					uuid=$(jq -r .client_uuid < /etc/backup/config.json)
 | 
				
			||||||
server=$(jq -r .server_hostname < /etc/backup/config.json)
 | 
					server=$(jq -r .server_hostname < /etc/backup/config.json)
 | 
				
			||||||
ssh="ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 backup-receiver@$server"
 | 
					ssh="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 backup-receiver@$server"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
source_dataset=$(zfs list -H -o mountpoint,name | grep -P "^$path\t" | cut -d $'\t' -f 2)
 | 
					source_dataset=$(zfs list -H -o mountpoint,name | grep -P "^$path\t" | cut -d $'\t' -f 2)
 | 
				
			||||||
target_dataset="tank/$uuid/$source_dataset"
 | 
					target_dataset="tank/$uuid/$source_dataset"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								bundles/ssh/files/ssh_config
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								bundles/ssh/files/ssh_config
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					Host *
 | 
				
			||||||
 | 
					    #UserKnownHostsFile ~/.ssh/known_hosts ~/.ssh/known_hosts.d/%k
 | 
				
			||||||
 | 
					    SendEnv LANG LC_*
 | 
				
			||||||
 | 
					    HashKnownHosts yes
 | 
				
			||||||
 | 
					    GSSAPIAuthentication yes
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,31 @@
 | 
				
			||||||
if not node.metadata.get('FIXME_dont_touch_sshd', False):
 | 
					if not node.metadata.get('FIXME_dont_touch_sshd', False):
 | 
				
			||||||
    # on debian bullseye raspberry images, starting the systemd ssh
 | 
					    # on debian bullseye raspberry images, starting the systemd ssh
 | 
				
			||||||
    # daemon seems to collide with an existing sysv daemon
 | 
					    # daemon seems to collide with an existing sysv daemon
 | 
				
			||||||
    files['/etc/ssh/sshd_config'] = {
 | 
					    directories = {
 | 
				
			||||||
 | 
					        '/etc/ssh': {
 | 
				
			||||||
 | 
					            'purge': True,
 | 
				
			||||||
 | 
					            'mode': '0755',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    files = {
 | 
				
			||||||
 | 
					        '/etc/ssh/moduli': {
 | 
				
			||||||
 | 
					            'content_type': 'any',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/ssh_config': {
 | 
				
			||||||
 | 
					            'triggers': [
 | 
				
			||||||
 | 
					                'svc_systemd:ssh:restart'
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/ssh_config': {
 | 
				
			||||||
 | 
					            'content_type': 'mako',
 | 
				
			||||||
 | 
					            'context': {
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'triggers': [
 | 
				
			||||||
 | 
					                'svc_systemd:ssh:restart'
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/sshd_config': {
 | 
				
			||||||
            'content_type': 'mako',
 | 
					            'content_type': 'mako',
 | 
				
			||||||
            'context': {
 | 
					            'context': {
 | 
				
			||||||
                'users': sorted(node.metadata.get('ssh/allow_users')),
 | 
					                'users': sorted(node.metadata.get('ssh/allow_users')),
 | 
				
			||||||
| 
						 | 
					@ -9,6 +33,29 @@ if not node.metadata.get('FIXME_dont_touch_sshd', False):
 | 
				
			||||||
            'triggers': [
 | 
					            'triggers': [
 | 
				
			||||||
                'svc_systemd:ssh:restart'
 | 
					                'svc_systemd:ssh:restart'
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/ssh_host_ed25519_key': {
 | 
				
			||||||
 | 
					            'content': node.metadata.get('ssh/host_key/private') + '\n',
 | 
				
			||||||
 | 
					            'mode': '0600',
 | 
				
			||||||
 | 
					            'triggers': [
 | 
				
			||||||
 | 
					                'svc_systemd:ssh:restart'
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/ssh_host_ed25519_key.pub': {
 | 
				
			||||||
 | 
					            'content': node.metadata.get('ssh/host_key/public') + '\n',
 | 
				
			||||||
 | 
					            'mode': '0644',
 | 
				
			||||||
 | 
					            'triggers': [
 | 
				
			||||||
 | 
					                'svc_systemd:ssh:restart'
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '/etc/ssh/ssh_known_hosts': {
 | 
				
			||||||
 | 
					            'content': '\n'.join(
 | 
				
			||||||
 | 
					                repo.libs.ssh.known_hosts_entry_for(other_node)
 | 
				
			||||||
 | 
					                    for other_node in sorted(repo.nodes)
 | 
				
			||||||
 | 
					                    if other_node != node
 | 
				
			||||||
 | 
					                    and other_node.has_bundle('ssh')
 | 
				
			||||||
 | 
					            ) + '\n',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    svc_systemd['ssh'] = {
 | 
					    svc_systemd['ssh'] = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
 | 
					from base64 import b64decode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@metadata_reactor.provides(
 | 
					@metadata_reactor.provides(
 | 
				
			||||||
    'ssh/allow_users',
 | 
					    'ssh/allow_users',
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -11,3 +14,21 @@ def users(metadata):
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@metadata_reactor.provides(
 | 
				
			||||||
 | 
					    'ssh/host_key',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					def host_key(metadata):
 | 
				
			||||||
 | 
					    private, public = repo.libs.ssh.generate_ed25519_key_pair(
 | 
				
			||||||
 | 
					        b64decode(str(repo.vault.random_bytes_as_base64_for(f"HostKey {metadata.get('id')}", length=32)))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        'ssh': {
 | 
				
			||||||
 | 
					            'host_key': {
 | 
				
			||||||
 | 
					                'private': private + '\n',
 | 
				
			||||||
 | 
					                'public': public + f' root@{node.name}',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										55
									
								
								doc/test_protect.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								doc/test_protect.service
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=TEST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=oneshot
 | 
				
			||||||
 | 
					ExecStart=/opt/test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DynamicUser=yes
 | 
				
			||||||
 | 
					UMask=077
 | 
				
			||||||
 | 
					ProtectSystem=strict
 | 
				
			||||||
 | 
					ProtectHome=yes
 | 
				
			||||||
 | 
					PrivateTmp=yes
 | 
				
			||||||
 | 
					PrivateDevices=yes # DevicePolicy=closed
 | 
				
			||||||
 | 
					PrivateNetwork=yes
 | 
				
			||||||
 | 
					IPAddressDeny=any
 | 
				
			||||||
 | 
					PrivateUsers=yes
 | 
				
			||||||
 | 
					ProtectHostname=yes
 | 
				
			||||||
 | 
					ProtectClock=yes
 | 
				
			||||||
 | 
					ProtectKernelTunables=yes
 | 
				
			||||||
 | 
					ProtectKernelModules=yes
 | 
				
			||||||
 | 
					ProtectKernelLogs=yes
 | 
				
			||||||
 | 
					ProtectControlGroups=yes
 | 
				
			||||||
 | 
					RestrictAddressFamilies=none
 | 
				
			||||||
 | 
					RestrictFileSystems=ext4 tmpfs zfs
 | 
				
			||||||
 | 
					RestrictNamespaces=yes
 | 
				
			||||||
 | 
					LockPersonality=yes
 | 
				
			||||||
 | 
					MemoryDenyWriteExecute=yes
 | 
				
			||||||
 | 
					RestrictRealtime=yes
 | 
				
			||||||
 | 
					RestrictSUIDSGID=yes
 | 
				
			||||||
 | 
					RemoveIPC=yes
 | 
				
			||||||
 | 
					PrivateMounts=yes
 | 
				
			||||||
 | 
					SystemCallFilter=~@swap
 | 
				
			||||||
 | 
					SystemCallFilter=~@resources
 | 
				
			||||||
 | 
					SystemCallFilter=~@reboot
 | 
				
			||||||
 | 
					SystemCallFilter=~@raw-io
 | 
				
			||||||
 | 
					SystemCallFilter=~@privileged
 | 
				
			||||||
 | 
					SystemCallFilter=~@obsolete
 | 
				
			||||||
 | 
					SystemCallFilter=~@mount
 | 
				
			||||||
 | 
					SystemCallFilter=~@module
 | 
				
			||||||
 | 
					SystemCallFilter=~@debug
 | 
				
			||||||
 | 
					SystemCallFilter=~@cpu-emulation
 | 
				
			||||||
 | 
					SystemCallFilter=~@clock
 | 
				
			||||||
 | 
					CapabilityBoundingSet=
 | 
				
			||||||
 | 
					ProtectProc=invisible
 | 
				
			||||||
 | 
					ProcSubset=pid
 | 
				
			||||||
 | 
					NoNewPrivileges=yes
 | 
				
			||||||
 | 
					SystemCallArchitectures=native
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReadOnlyPaths=/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NoExecPaths=/
 | 
				
			||||||
 | 
					ExecPaths=/opt/test /bin/bash /lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
| 
						 | 
					@ -5,36 +5,51 @@ Description=TEST
 | 
				
			||||||
Type=oneshot
 | 
					Type=oneshot
 | 
				
			||||||
ExecStart=/opt/test
 | 
					ExecStart=/opt/test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# user
 | 
				
			||||||
 | 
					UMask=077
 | 
				
			||||||
 | 
					DynamicUser=yes
 | 
				
			||||||
 | 
					PrivateUsers=yes
 | 
				
			||||||
 | 
					RestrictSUIDSGID=yes
 | 
				
			||||||
 | 
					NoNewPrivileges=yes
 | 
				
			||||||
 | 
					LockPersonality=yes
 | 
				
			||||||
 | 
					RemoveIPC=yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# fs
 | 
				
			||||||
ProtectSystem=strict
 | 
					ProtectSystem=strict
 | 
				
			||||||
ProtectHome=yes
 | 
					ProtectHome=yes
 | 
				
			||||||
PrivateTmp=yes
 | 
					PrivateTmp=yes
 | 
				
			||||||
PrivateDevices=yes
 | 
					PrivateDevices=yes
 | 
				
			||||||
PrivateNetwork=yes
 | 
					PrivateNetwork=yes
 | 
				
			||||||
PrivateUsers=yes
 | 
					ProtectProc=invisible
 | 
				
			||||||
 | 
					ProcSubset=pid
 | 
				
			||||||
 | 
					PrivateMounts=yes
 | 
				
			||||||
 | 
					RestrictFileSystems=ext4 tmpfs zfs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NoExecPaths=/
 | 
				
			||||||
 | 
					ExecPaths=/opt/test /bin /lib /lib64 /usr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TemporaryFileSystem=/var
 | 
				
			||||||
 | 
					TemporaryFileSystem=/var
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# network
 | 
				
			||||||
 | 
					IPAddressDeny=any
 | 
				
			||||||
 | 
					RestrictAddressFamilies=none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# syscall
 | 
				
			||||||
 | 
					SystemCallArchitectures=native
 | 
				
			||||||
 | 
					SystemCallFilter=~@swap ~@resources ~@reboot ~@raw-io ~@privileged ~@obsolete ~@mount ~@module ~@debug ~@cpu-emulation ~@clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# else
 | 
				
			||||||
ProtectHostname=yes
 | 
					ProtectHostname=yes
 | 
				
			||||||
ProtectClock=yes
 | 
					ProtectClock=yes
 | 
				
			||||||
ProtectKernelTunables=yes
 | 
					ProtectKernelTunables=yes
 | 
				
			||||||
ProtectKernelModules=yes
 | 
					ProtectKernelModules=yes
 | 
				
			||||||
ProtectKernelLogs=yes
 | 
					ProtectKernelLogs=yes
 | 
				
			||||||
ProtectControlGroups=yes
 | 
					ProtectControlGroups=yes
 | 
				
			||||||
RestrictAddressFamilies=none
 | 
					 | 
				
			||||||
RestrictFileSystems=ext4 tmpfs zfs
 | 
					 | 
				
			||||||
RestrictNamespaces=yes
 | 
					RestrictNamespaces=yes
 | 
				
			||||||
LockPersonality=yes
 | 
					 | 
				
			||||||
MemoryDenyWriteExecute=yes
 | 
					MemoryDenyWriteExecute=yes
 | 
				
			||||||
RestrictRealtime=yes
 | 
					RestrictRealtime=yes
 | 
				
			||||||
RestrictSUIDSGID=yes
 | 
					 | 
				
			||||||
RemoveIPC=yes
 | 
					 | 
				
			||||||
PrivateMounts=yes
 | 
					 | 
				
			||||||
SystemCallFilter=
 | 
					 | 
				
			||||||
SystemCallArchitectures=native
 | 
					 | 
				
			||||||
CapabilityBoundingSet=
 | 
					CapabilityBoundingSet=
 | 
				
			||||||
ProtectProc=invisible
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ReadOnlyPaths=/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
NoExecPaths=/
 | 
					 | 
				
			||||||
ExecPaths=/opt/test /bin/bash /lib
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[Install]
 | 
					[Install]
 | 
				
			||||||
WantedBy=multi-user.target
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
							
								
								
									
										54
									
								
								doc/test_temp.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								doc/test_temp.service
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=TEST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=oneshot
 | 
				
			||||||
 | 
					ExecStart=/opt/test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TemporaryFileSystem=/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BindReadOnlyPaths=/opt/test /bin /lib /lib64 /usr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					UMask=077
 | 
				
			||||||
 | 
					ProtectHome=yes
 | 
				
			||||||
 | 
					PrivateTmp=yes
 | 
				
			||||||
 | 
					PrivateDevices=yes
 | 
				
			||||||
 | 
					PrivateNetwork=yes
 | 
				
			||||||
 | 
					IPAddressDeny=any
 | 
				
			||||||
 | 
					ProtectHostname=yes
 | 
				
			||||||
 | 
					ProtectClock=yes
 | 
				
			||||||
 | 
					ProtectKernelTunables=yes
 | 
				
			||||||
 | 
					ProtectKernelModules=yes
 | 
				
			||||||
 | 
					ProtectKernelLogs=yes
 | 
				
			||||||
 | 
					ProtectControlGroups=yes
 | 
				
			||||||
 | 
					RestrictAddressFamilies=none
 | 
				
			||||||
 | 
					RestrictFileSystems=ext4 tmpfs zfs
 | 
				
			||||||
 | 
					RestrictNamespaces=yes
 | 
				
			||||||
 | 
					LockPersonality=yes
 | 
				
			||||||
 | 
					MemoryDenyWriteExecute=yes
 | 
				
			||||||
 | 
					RestrictRealtime=yes
 | 
				
			||||||
 | 
					RestrictSUIDSGID=yes
 | 
				
			||||||
 | 
					RemoveIPC=yes
 | 
				
			||||||
 | 
					PrivateMounts=yes
 | 
				
			||||||
 | 
					SystemCallFilter=~@swap
 | 
				
			||||||
 | 
					SystemCallFilter=~@resources
 | 
				
			||||||
 | 
					SystemCallFilter=~@reboot
 | 
				
			||||||
 | 
					SystemCallFilter=~@raw-io
 | 
				
			||||||
 | 
					SystemCallFilter=~@privileged
 | 
				
			||||||
 | 
					SystemCallFilter=~@obsolete
 | 
				
			||||||
 | 
					SystemCallFilter=~@mount
 | 
				
			||||||
 | 
					SystemCallFilter=~@module
 | 
				
			||||||
 | 
					SystemCallFilter=~@debug
 | 
				
			||||||
 | 
					SystemCallFilter=~@cpu-emulation
 | 
				
			||||||
 | 
					SystemCallFilter=~@clock
 | 
				
			||||||
 | 
					CapabilityBoundingSet=
 | 
				
			||||||
 | 
					ProtectProc=invisible
 | 
				
			||||||
 | 
					ProcSubset=pid
 | 
				
			||||||
 | 
					NoNewPrivileges=yes
 | 
				
			||||||
 | 
					SystemCallArchitectures=native
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
							
								
								
									
										44
									
								
								libs/ssh.py
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								libs/ssh.py
									
									
									
									
									
								
							| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
from base64 import b64decode, b64encode
 | 
					from base64 import b64decode, b64encode
 | 
				
			||||||
from hashlib import sha3_224
 | 
					from hashlib import sha3_224, sha1
 | 
				
			||||||
from functools import cache
 | 
					from functools import cache
 | 
				
			||||||
 | 
					import hmac
 | 
				
			||||||
 | 
					from ipaddress import ip_interface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 | 
					from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 | 
				
			||||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, PublicFormat, NoEncryption
 | 
					from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, PublicFormat, NoEncryption
 | 
				
			||||||
| 
						 | 
					@ -46,3 +48,43 @@ def generate_ed25519_key_pair(secret):
 | 
				
			||||||
    # RETURN
 | 
					    # RETURN
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    return (deterministic_privatekey, public_key)
 | 
					    return (deterministic_privatekey, public_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#https://www.fragmentationneeded.net/2017/10/ssh-hashknownhosts-file-format.html
 | 
				
			||||||
 | 
					# test this:
 | 
				
			||||||
 | 
					# - `ssh-keyscan -H 10.0.0.5`
 | 
				
			||||||
 | 
					# - take the salt from the ssh-ed25519 entry (first field after '|1|')
 | 
				
			||||||
 | 
					# - `bw debug -c 'repo.libs.ssh.known_hosts_entry_for(repo.get_node(<node with hostname 10.0.0.5>), <salt from ssh-keygen>)'`
 | 
				
			||||||
 | 
					@cache
 | 
				
			||||||
 | 
					def known_hosts_entry_for(node, test_salt=None):
 | 
				
			||||||
 | 
					    ips = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for network in node.metadata.get('network').values():
 | 
				
			||||||
 | 
					        if network.get('ipv4', None):
 | 
				
			||||||
 | 
					            ips.add(str(ip_interface(network['ipv4']).ip))
 | 
				
			||||||
 | 
					        if network.get('ipv6', None):
 | 
				
			||||||
 | 
					            ips.add(str(ip_interface(network['ipv6']).ip))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    domains = {
 | 
				
			||||||
 | 
					        domain
 | 
				
			||||||
 | 
					            for domain, records in node.metadata.get('dns').items()
 | 
				
			||||||
 | 
					            for type, values in records.items()
 | 
				
			||||||
 | 
					            if type in {'A', 'AAAA'}
 | 
				
			||||||
 | 
					            and set(values) & ips
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lines = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for hostname in {node.hostname, *ips, *domains}:
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        if test_salt:
 | 
				
			||||||
 | 
					            salt = b64decode(test_salt)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            salt = sha1(node.metadata.get('id').encode()).digest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hash = hmac.new(salt, hostname.encode(), sha1).digest()
 | 
				
			||||||
 | 
					        pubkey = node.metadata.get('ssh/host_key/public')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        lines.add(f'|1|{b64encode(salt).decode()}|{b64encode(hash).decode()} {" ".join(pubkey.split()[:2])}')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    return '\n'.join(sorted(lines))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ template = '''
 | 
				
			||||||
# ${segment.split('#', 2)[1]}
 | 
					# ${segment.split('#', 2)[1]}
 | 
				
			||||||
%     endif
 | 
					%     endif
 | 
				
			||||||
[${segment.split('#')[0]}]
 | 
					[${segment.split('#')[0]}]
 | 
				
			||||||
%     for option, value in options.items():
 | 
					%     for option, value in sorted(options.items()):
 | 
				
			||||||
%         if isinstance(value, dict):
 | 
					%         if isinstance(value, dict):
 | 
				
			||||||
%             for k, v in value.items():
 | 
					%             for k, v in value.items():
 | 
				
			||||||
${option}=${k}=${v}
 | 
					${option}=${k}=${v}
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ ${option}=${k}=${v}
 | 
				
			||||||
%             for item in sorted(value):
 | 
					%             for item in sorted(value):
 | 
				
			||||||
${option}=${item}
 | 
					${option}=${item}
 | 
				
			||||||
%             endfor
 | 
					%             endfor
 | 
				
			||||||
 | 
					%         elif isinstance(value, type(None)):
 | 
				
			||||||
%         else:
 | 
					%         else:
 | 
				
			||||||
${option}=${str(value)}
 | 
					${option}=${str(value)}
 | 
				
			||||||
%         endif
 | 
					%         endif
 | 
				
			||||||
| 
						 | 
					@ -39,5 +40,53 @@ def segment_order(segment):
 | 
				
			||||||
def generate_unitfile(data):
 | 
					def generate_unitfile(data):
 | 
				
			||||||
    return Template(template).render(
 | 
					    return Template(template).render(
 | 
				
			||||||
        data=dict(sorted(data.items(), key=segment_order)),
 | 
					        data=dict(sorted(data.items(), key=segment_order)),
 | 
				
			||||||
        order=order
 | 
					 | 
				
			||||||
    ).lstrip()
 | 
					    ).lstrip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# wip
 | 
				
			||||||
 | 
					def protection():
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        # user
 | 
				
			||||||
 | 
					        'UMask': '077',
 | 
				
			||||||
 | 
					        'DynamicUser': 'yes',
 | 
				
			||||||
 | 
					        'PrivateUsers': 'yes',
 | 
				
			||||||
 | 
					        'RestrictSUIDSGID': 'yes',
 | 
				
			||||||
 | 
					        'NoNewPrivileges': 'yes',
 | 
				
			||||||
 | 
					        'LockPersonality': 'yes',
 | 
				
			||||||
 | 
					        'RemoveIPC': 'yes',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fs
 | 
				
			||||||
 | 
					        'ProtectSystem': 'strict',
 | 
				
			||||||
 | 
					        'ProtectHome': 'yes',
 | 
				
			||||||
 | 
					        'PrivateTmp': 'yes',
 | 
				
			||||||
 | 
					        'PrivateDevices': 'yes',
 | 
				
			||||||
 | 
					        'ProtectProc': 'invisible',
 | 
				
			||||||
 | 
					        'ProcSubset': 'pid',
 | 
				
			||||||
 | 
					        'PrivateMounts': 'yes',
 | 
				
			||||||
 | 
					        'RestrictFileSystems': {'ext4', 'tmpfs', 'zfs'},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'NoExecPaths': {'/'},
 | 
				
			||||||
 | 
					        'ExecPaths': {'/bin', '/sbin', '/lib', '/lib64', '/usr'},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'TemporaryFileSystem': {'/var'},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # network
 | 
				
			||||||
 | 
					        'IPAddressDeny': 'any',
 | 
				
			||||||
 | 
					        'PrivateNetwork': 'yes',
 | 
				
			||||||
 | 
					        'RestrictAddressFamilies': 'none',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # syscall
 | 
				
			||||||
 | 
					        'SystemCallArchitectures': 'native',
 | 
				
			||||||
 | 
					        'SystemCallFilter': '~@swap @resources @reboot @raw-io @privileged @obsolete @mount @module @debug @cpu-emulation @clock',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # else
 | 
				
			||||||
 | 
					        'ProtectHostname': 'yes',
 | 
				
			||||||
 | 
					        'ProtectClock': 'yes',
 | 
				
			||||||
 | 
					        'ProtectKernelTunables': 'yes',
 | 
				
			||||||
 | 
					        'ProtectKernelModules': 'yes',
 | 
				
			||||||
 | 
					        'ProtectKernelLogs': 'yes',
 | 
				
			||||||
 | 
					        'ProtectControlGroups': 'yes',
 | 
				
			||||||
 | 
					        'RestrictNamespaces': 'yes',
 | 
				
			||||||
 | 
					        'MemoryDenyWriteExecute': 'yes',
 | 
				
			||||||
 | 
					        'RestrictRealtime': 'yes',
 | 
				
			||||||
 | 
					        'CapabilityBoundingSet': '',
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue