do i want to...
This commit is contained in:
parent
083c966cad
commit
7fc416f17c
9
aif.xsd
9
aif.xsd
@ -452,6 +452,7 @@
|
|||||||
<xs:element name="passphrase" minOccurs="0"
|
<xs:element name="passphrase" minOccurs="0"
|
||||||
maxOccurs="unbounded"
|
maxOccurs="unbounded"
|
||||||
type="aif:t_nonempty"/>
|
type="aif:t_nonempty"/>
|
||||||
|
<!-- TODO: support URI to *read* bytes from? -->
|
||||||
<xs:element name="keyFile" minOccurs="0"
|
<xs:element name="keyFile" minOccurs="0"
|
||||||
maxOccurs="unbounded">
|
maxOccurs="unbounded">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
@ -471,6 +472,14 @@
|
|||||||
<xs:attribute name="id" type="xs:ID" use="required"/>
|
<xs:attribute name="id" type="xs:ID" use="required"/>
|
||||||
<xs:attribute name="name" type="aif:t_nonempty" use="required"/>
|
<xs:attribute name="name" type="aif:t_nonempty" use="required"/>
|
||||||
<xs:attribute name="source" type="xs:IDREF" use="required"/>
|
<xs:attribute name="source" type="xs:IDREF" use="required"/>
|
||||||
|
<!-- It's too much of a PITA to validate the next three.
|
||||||
|
We do it in runtime. -->
|
||||||
|
<xs:attribute name="cipher" type="aif:t_nonempty"
|
||||||
|
use="optional" default="aes-xtc-plain64"/>
|
||||||
|
<xs:attribute name="keySize" type="xs:positiveInteger"
|
||||||
|
use="optional" default="512"/>
|
||||||
|
<xs:attribute name="hash" type="aif:t_nonempty"
|
||||||
|
use="optional" default="sha512"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
import subprocess # I wish there was a better way to get the supported LUKS ciphers.
|
||||||
import uuid
|
import uuid
|
||||||
##
|
##
|
||||||
import parted # https://www.gnu.org/software/parted/api/index.html
|
import parted # https://www.gnu.org/software/parted/api/index.html
|
||||||
|
@ -22,7 +22,7 @@ class Partition(object):
|
|||||||
'primary, extended, or logical partition for msdos partition tables'))
|
'primary, extended, or logical partition for msdos partition tables'))
|
||||||
aif.disk._common.addBDPlugin('part')
|
aif.disk._common.addBDPlugin('part')
|
||||||
self.xml = part_xml
|
self.xml = part_xml
|
||||||
self.id = part_xml.attrib['id']
|
self.id = self.xml.attrib['id']
|
||||||
self.table_type = getattr(_BlockDev.PartTableType, tbltype.upper())
|
self.table_type = getattr(_BlockDev.PartTableType, tbltype.upper())
|
||||||
if tbltype == 'msdos':
|
if tbltype == 'msdos':
|
||||||
# Could technically be _BlockDev.PartTypeReq.NEXT BUT that doesn't *quite* work
|
# Could technically be _BlockDev.PartTypeReq.NEXT BUT that doesn't *quite* work
|
||||||
|
141
aif/disk/luks.py
141
aif/disk/luks.py
@ -1,3 +1,6 @@
|
|||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
##
|
||||||
from . import _common
|
from . import _common
|
||||||
import aif.disk.block as block
|
import aif.disk.block as block
|
||||||
import aif.disk.lvm as lvm
|
import aif.disk.lvm as lvm
|
||||||
@ -7,9 +10,145 @@ import aif.disk.mdadm as mdadm
|
|||||||
_BlockDev = _common.BlockDev
|
_BlockDev = _common.BlockDev
|
||||||
|
|
||||||
|
|
||||||
|
class LuksSecret(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
_common.addBDPlugin('crypto')
|
||||||
|
self.passphrase = None
|
||||||
|
self.size = 4096
|
||||||
|
self.path = None
|
||||||
|
|
||||||
|
|
||||||
|
class LuksSecretPassphrase(LuksSecret):
|
||||||
|
def __init__(self, passphrase):
|
||||||
|
super().__init__()
|
||||||
|
self.passphrase = passphrase
|
||||||
|
|
||||||
|
|
||||||
|
class LuksSecretFile(LuksSecret):
|
||||||
|
# TODO: might do a little tweaking in a later release to support *reading from* bytes.
|
||||||
|
def __init__(self, path, passphrase = None, bytesize = 4096):
|
||||||
|
super().__init__()
|
||||||
|
self.path = os.path.realpath(path)
|
||||||
|
self.passphrase = passphrase
|
||||||
|
self.size = bytesize # only used if passphrase == None
|
||||||
|
self._genSecret()
|
||||||
|
|
||||||
|
def _genSecret(self):
|
||||||
|
if not self.passphrase:
|
||||||
|
# TODO: is secrets.token_bytes safe for *persistent* random data?
|
||||||
|
self.passphrase = secrets.token_bytes(self.size)
|
||||||
|
if not isinstance(self.passphrase, bytes):
|
||||||
|
self.passphrase = self.passphrase.encode('utf-8')
|
||||||
|
return()
|
||||||
|
|
||||||
|
|
||||||
class LUKS(object):
|
class LUKS(object):
|
||||||
def __init__(self, luks_xml, partobj):
|
def __init__(self, luks_xml, partobj):
|
||||||
self.xml = luks_xml
|
self.xml = luks_xml
|
||||||
|
self.id = self.xml.attrib['id']
|
||||||
|
self.name = self.xml.attrib['name']
|
||||||
|
self.device = partobj
|
||||||
|
self.source = self.device.devpath
|
||||||
|
self.secrets = []
|
||||||
|
self.created = False
|
||||||
|
self.locked = True
|
||||||
|
if not isinstance(self.device, (block.Disk,
|
||||||
|
block.Partition,
|
||||||
|
lvm.LV,
|
||||||
|
mdadm.Array)):
|
||||||
|
raise ValueError(('partobj must be of type '
|
||||||
|
'aif.disk.block.Disk, '
|
||||||
|
'aif.disk.block.Partition, '
|
||||||
|
'aif.disk.lvm.LV, or'
|
||||||
|
'aif.disk.mdadm.Array'))
|
||||||
_common.addBDPlugin('crypto')
|
_common.addBDPlugin('crypto')
|
||||||
self.devpath = None
|
self.devpath = '/dev/mapper/{0}'.format(self.name)
|
||||||
|
|
||||||
|
def addSecret(self, secretobj):
|
||||||
|
if not isinstance(secretobj, LuksSecret):
|
||||||
|
raise ValueError('secretobj must be of type aif.disk.luks.LuksSecret '
|
||||||
|
'(aif.disk.luks.LuksSecretPassphrase or '
|
||||||
|
'aif.disk.luks.LuksSecretFile)')
|
||||||
|
self.secrets.append(secretobj)
|
||||||
|
return()
|
||||||
|
|
||||||
|
def createSecret(self, secrets_xml = None):
|
||||||
|
if not secrets_xml: # Find all of them from self
|
||||||
|
for secret in self.xml.findall('secrets'):
|
||||||
|
secretobj = None
|
||||||
|
secrettypes = set()
|
||||||
|
for s in secret.iterchildren():
|
||||||
|
secrettypes.add(s.tag)
|
||||||
|
if all((('passphrase' in secrettypes),
|
||||||
|
('keyFile' in secrettypes))):
|
||||||
|
# This is safe, because a valid config only has at most one of both types.
|
||||||
|
kf = secret.find('keyFile')
|
||||||
|
secretobj = LuksSecretFile(kf.text, # path
|
||||||
|
passphrase = secret.find('passphrase').text,
|
||||||
|
bytesize = kf.attrib.get('size', 4096)) # TECHNICALLY should be a no-op.
|
||||||
|
elif 'passphrase' in secrettypes:
|
||||||
|
secretobj = LuksSecretPassphrase(secret.find('passphrase').text)
|
||||||
|
elif 'keyFile' in secrettypes:
|
||||||
|
kf = secret.find('keyFile')
|
||||||
|
secretobj = LuksSecretFile(kf.text,
|
||||||
|
passphrase = None,
|
||||||
|
bytesize = kf.attrib.get('size', 4096))
|
||||||
|
self.secrets.append(secretobj)
|
||||||
|
else:
|
||||||
|
secretobj = None
|
||||||
|
secrettypes = set()
|
||||||
|
for s in secrets_xml.iterchildren():
|
||||||
|
secrettypes.add(s.tag)
|
||||||
|
if all((('passphrase' in secrettypes),
|
||||||
|
('keyFile' in secrettypes))):
|
||||||
|
# This is safe, because a valid config only has at most one of both types.
|
||||||
|
kf = secrets_xml.find('keyFile')
|
||||||
|
secretobj = LuksSecretFile(kf.text, # path
|
||||||
|
passphrase = secrets_xml.find('passphrase').text,
|
||||||
|
bytesize = kf.attrib.get('size', 4096)) # TECHNICALLY should be a no-op.
|
||||||
|
elif 'passphrase' in secrettypes:
|
||||||
|
secretobj = LuksSecretPassphrase(secrets_xml.find('passphrase').text)
|
||||||
|
elif 'keyFile' in secrettypes:
|
||||||
|
kf = secrets_xml.find('keyFile')
|
||||||
|
secretobj = LuksSecretFile(kf.text,
|
||||||
|
passphrase = None,
|
||||||
|
bytesize = kf.attrib.get('size', 4096))
|
||||||
|
self.secrets.append(secretobj)
|
||||||
|
return()
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
if not self.secrets:
|
||||||
|
raise RuntimeError('Cannot create a LUKS volume with no secrets added')
|
||||||
|
for idx, secret in enumerate(self.secrets):
|
||||||
|
if idx == 0:
|
||||||
|
_BlockDev.crypto.luks_format_luks2(self.source,
|
||||||
|
self.cipher,
|
||||||
|
self.keysize,
|
||||||
|
self.passphrase,
|
||||||
|
self.key_file,
|
||||||
|
self.min_entropy)
|
||||||
|
else:
|
||||||
|
pass # TODO: *add* keyfile/passphrase/whatev.
|
||||||
pass
|
pass
|
||||||
|
self.created = True
|
||||||
|
return()
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
if not self.created:
|
||||||
|
raise RuntimeError('Cannot lock a LUKS volume before it is created')
|
||||||
|
if self.locked:
|
||||||
|
return()
|
||||||
|
|
||||||
|
pass
|
||||||
|
self.locked = True
|
||||||
|
return()
|
||||||
|
|
||||||
|
def unlock(self, passphrase = None):
|
||||||
|
if not self.created:
|
||||||
|
raise RuntimeError('Cannot unlock a LUKS volume before it is created')
|
||||||
|
if not self.locked:
|
||||||
|
return()
|
||||||
|
|
||||||
|
pass
|
||||||
|
self.locked = False
|
||||||
|
return()
|
||||||
|
@ -74,7 +74,7 @@ class Member(object):
|
|||||||
class Array(object):
|
class Array(object):
|
||||||
def __init__(self, array_xml, homehost, devpath = None):
|
def __init__(self, array_xml, homehost, devpath = None):
|
||||||
self.xml = array_xml
|
self.xml = array_xml
|
||||||
self.id = array_xml.attrib['id']
|
self.id = self.xml.attrib['id']
|
||||||
self.level = int(self.xml.attrib['level'])
|
self.level = int(self.xml.attrib['level'])
|
||||||
if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS:
|
if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS:
|
||||||
raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i)
|
raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i)
|
||||||
|
@ -129,7 +129,7 @@ class Member(object):
|
|||||||
class Array(object):
|
class Array(object):
|
||||||
def __init__(self, array_xml, homehost, devpath = None):
|
def __init__(self, array_xml, homehost, devpath = None):
|
||||||
self.xml = array_xml
|
self.xml = array_xml
|
||||||
self.id = array_xml.attrib['id']
|
self.id = self.xml.attrib['id']
|
||||||
self.level = int(self.xml.attrib['level'])
|
self.level = int(self.xml.attrib['level'])
|
||||||
if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS:
|
if self.level not in aif.constants.MDADM_SUPPORTED_LEVELS:
|
||||||
raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i)
|
raise ValueError('RAID level must be one of: {0}'.format(', '.join([str(i)
|
||||||
|
Loading…
Reference in New Issue
Block a user