Add script to install OpenSSH server
This commit is contained in:
parent
ddc316ad9e
commit
c0b46695b5
466
ntfs_acl.py
Normal file
466
ntfs_acl.py
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
|
||||||
|
import sys, os
|
||||||
|
from io import BytesIO
|
||||||
|
from construct import *
|
||||||
|
from pathlib import Path # overrides same name from construct
|
||||||
|
import ctypes
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
def dump_sd(path):
|
||||||
|
return getxattr(path, "system.ntfs_acl")
|
||||||
|
|
||||||
|
def write_sd(path, sd):
|
||||||
|
if isinstance(sd, SecurityDescriptor): sd = sd.build()
|
||||||
|
elif isinstance(sd, Container): sd = SecurityDescriptorStruct.build(sd)
|
||||||
|
os.setxattr(path, "system.ntfs_acl", sd)
|
||||||
|
|
||||||
|
_cdll = ctypes.CDLL(None)
|
||||||
|
|
||||||
|
#_cdll.libcnotify_verbose_set(1)
|
||||||
|
|
||||||
|
def getxattr(path, name):
|
||||||
|
# We have to use ctypes instead of os.getxattr, because os.getxattr uses a 128B buffer,
|
||||||
|
# which is too small (ironically, it retries with larger buffer if it gets ERANGE, but
|
||||||
|
# ntfs-3g returns EIO instead of ERANGE when buffer is too small). It cannot be forced
|
||||||
|
# to use larger buffer by default.
|
||||||
|
if isinstance(name, str):
|
||||||
|
name = name.encode('ascii')
|
||||||
|
if isinstance(path, os.PathLike):
|
||||||
|
path = str(path)
|
||||||
|
if isinstance(path, str):
|
||||||
|
path = path.encode(sys.getfilesystemencoding())
|
||||||
|
global _cdll
|
||||||
|
size = _cdll.getxattr(path, name, None, 0)
|
||||||
|
buf = ctypes.create_string_buffer(b'', size)
|
||||||
|
_cdll.getxattr(path, name, buf, size)
|
||||||
|
return buf.raw
|
||||||
|
|
||||||
|
|
||||||
|
#class ClassAdapter(Adapter):
|
||||||
|
# def __init__(self, cls, *a, **kw):
|
||||||
|
# self.adapt_cls = cls
|
||||||
|
# super().__init__(*a, **kw)
|
||||||
|
#
|
||||||
|
# def _decode(self, obj, context, path):
|
||||||
|
# return self.adapt_cls.from_construct(obj)
|
||||||
|
#
|
||||||
|
# def _encode(self, obj, context, path):
|
||||||
|
# if not isinstance(obj, self.adapt_cls): raise TypeError
|
||||||
|
# return obj.to_construct()
|
||||||
|
#
|
||||||
|
#class ACLAdapter(Adapter):
|
||||||
|
# def _decode(self, obj, context, path):
|
||||||
|
# assert len(obj.items) == obj.cnt
|
||||||
|
# return obj.items
|
||||||
|
#
|
||||||
|
# def _encode(self, obj, context, path):
|
||||||
|
# return dict(revision=2, cnt=len(obj), items=obj)
|
||||||
|
|
||||||
|
|
||||||
|
def _getpath(obj, path):
|
||||||
|
if isinstance(path, str): path = path.split('.')
|
||||||
|
for c in path:
|
||||||
|
obj = obj[c]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def _setpath(obj, path, val):
|
||||||
|
if isinstance(path, str): path = path.split('.')
|
||||||
|
for c in path[:-1]:
|
||||||
|
obj = obj[c]
|
||||||
|
obj[path[-1]] = val
|
||||||
|
|
||||||
|
class WithSize(Subconstruct):
|
||||||
|
def __init__(self, size_path, subcon, pattern=b"\x00"):
|
||||||
|
if not isinstance(pattern, bytes) or len(pattern) != 1:
|
||||||
|
raise PaddingError("pattern expected to be bytes of length 1")
|
||||||
|
super().__init__(subcon)
|
||||||
|
self.size_path = size_path
|
||||||
|
self.pattern = pattern
|
||||||
|
|
||||||
|
def _parse(self, stream, context, path):
|
||||||
|
position1 = stream.tell()
|
||||||
|
obj = self.subcon._parsereport(stream, context, path)
|
||||||
|
#print("obj:", obj)
|
||||||
|
position2 = stream.tell()
|
||||||
|
|
||||||
|
actual_length = (position2 - position1)
|
||||||
|
formal_length = _getpath(obj, self.size_path)
|
||||||
|
if formal_length < 0:
|
||||||
|
raise PaddingError("length cannot be negative", path=path)
|
||||||
|
pad = formal_length - actual_length
|
||||||
|
#print("Path:", path)
|
||||||
|
#print("Subcon:", self.subcon, self is ACEStruct, self is ACLStruct)
|
||||||
|
#print("Formal length:", formal_length)
|
||||||
|
#print("Actual length:", actual_length, position1, "..", position2)
|
||||||
|
if pad < 0:
|
||||||
|
raise PaddingError("subcon parsed %d bytes but was allowed only %d" % (actual_length, formal_length), path=path)
|
||||||
|
stream_read(stream, pad, path)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def _build(self, obj, stream, context, path):
|
||||||
|
tmpstream = BytesIO()
|
||||||
|
self.subcon._build(obj, tmpstream, context, path)
|
||||||
|
size = len(tmpstream.getvalue())
|
||||||
|
_setpath(obj, self.size_path, size)
|
||||||
|
#print(size, obj)
|
||||||
|
return self.subcon._build(obj, stream, context, path)
|
||||||
|
|
||||||
|
def _sizeof(self, context, path):
|
||||||
|
return self.subcon._sizeof(context, path)
|
||||||
|
|
||||||
|
|
||||||
|
# https://github.com/tuxera/ntfs-3g/blob/a4a837025b6ac2b0c44c93e34e22535fe9e95b27/ntfsprogs/ntfssecaudit.c#L1229
|
||||||
|
SDHeaderStruct = Struct(
|
||||||
|
"revision" / Const(1, Byte),
|
||||||
|
Padding(1),
|
||||||
|
"flags" / FlagsEnum(Int16ul,
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-control
|
||||||
|
SE_OWNER_DEFAULTED = 0x0001,
|
||||||
|
SE_GROUP_DEFAULTED = 0x0002,
|
||||||
|
SE_DACL_PRESENT = 0x0004,
|
||||||
|
SE_DACL_DEFAULTED = 0x0008,
|
||||||
|
SE_SACL_DEFAULTED = 0x0008,
|
||||||
|
SE_SACL_PRESENT = 0x0010,
|
||||||
|
SE_DACL_AUTO_INHERIT_REQ = 0x0100,
|
||||||
|
SE_SACL_AUTO_INHERIT_REQ = 0x0200,
|
||||||
|
SE_DACL_AUTO_INHERITED = 0x0400,
|
||||||
|
SE_SACL_AUTO_INHERITED = 0x0800,
|
||||||
|
SE_DACL_PROTECTED = 0x1000,
|
||||||
|
SE_SACL_PROTECTED = 0x2000,
|
||||||
|
SE_RM_CONTROL_VALID = 0x4000,
|
||||||
|
SE_SELF_RELATIVE = 0x8000,
|
||||||
|
),
|
||||||
|
"owner_sid_offset" / Int32ul,
|
||||||
|
"group_sid_offset" / Int32ul,
|
||||||
|
"sacl_offset" / Int32ul,
|
||||||
|
"dacl_offset" / Int32ul,
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components
|
||||||
|
# https://github.com/tuxera/ntfs-3g/blob/a4a837025b6ac2b0c44c93e34e22535fe9e95b27/ntfsprogs/ntfssecaudit.c#L957
|
||||||
|
SIDStruct = Struct(
|
||||||
|
"revision" / Const(1, Byte),
|
||||||
|
"cnt" / Byte,
|
||||||
|
"auth" / BytesInteger(6), # strangely, this is big endian, unlike other fields
|
||||||
|
"subauth" / Int32ul[this.cnt],
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-acl
|
||||||
|
# https://github.com/tuxera/ntfs-3g/blob/a4a837025b6ac2b0c44c93e34e22535fe9e95b27/ntfsprogs/ntfssecaudit.c#L1420
|
||||||
|
ACLHeaderStruct = Struct(
|
||||||
|
"revision" / Byte, # Const(2, Byte),
|
||||||
|
Padding(1),
|
||||||
|
"size" / Int16ul,
|
||||||
|
"cnt" / Int16ul,
|
||||||
|
Padding(2),
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/ace
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
|
||||||
|
ACCESS_MAX_MS_V2_ACE_TYPE = 0x3
|
||||||
|
ACCESS_MAX_MS_V3_ACE_TYPE = 0x4
|
||||||
|
ACCESS_MAX_MS_V4_ACE_TYPE = 0x8
|
||||||
|
ACCESS_MAX_MS_V5_ACE_TYPE = 0x14
|
||||||
|
ACEHeaderStruct = Struct(
|
||||||
|
"type" / Enum(Byte,
|
||||||
|
# from wine's winnt.h
|
||||||
|
ACCESS_ALLOWED_ACE_TYPE = 0x0,
|
||||||
|
ACCESS_DENIED_ACE_TYPE = 0x1,
|
||||||
|
SYSTEM_AUDIT_ACE_TYPE = 0x2,
|
||||||
|
SYSTEM_ALARM_ACE_TYPE = 0x3,
|
||||||
|
ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 0x4,
|
||||||
|
ACCESS_ALLOWED_OBJECT_ACE_TYPE = 0x5,
|
||||||
|
ACCESS_DENIED_OBJECT_ACE_TYPE = 0x6,
|
||||||
|
ACCESS_AUDIT_OBJECT_ACE_TYPE = 0x7,
|
||||||
|
ACCESS_ALARM_OBJECT_ACE_TYPE = 0x8,
|
||||||
|
ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 0x9,
|
||||||
|
ACCESS_DENIED_CALLBACK_ACE_TYPE = 0xa,
|
||||||
|
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 0xb,
|
||||||
|
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 0xc,
|
||||||
|
SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 0xd,
|
||||||
|
SYSTEM_ALARM_CALLBACK_ACE_TYPE = 0xe,
|
||||||
|
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 0xf,
|
||||||
|
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 0x10,
|
||||||
|
SYSTEM_MANDATORY_LABEL_ACE_TYPE = 0x11,
|
||||||
|
SYSTEM_RESOURCE_ATTRIBUTE_ACE_TYPE = 0x12,
|
||||||
|
SYSTEM_SCOPED_POLICY_ID_ACE_TYPE = 0x13,
|
||||||
|
SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE = 0x14,
|
||||||
|
),
|
||||||
|
"flags" / FlagsEnum(Byte,
|
||||||
|
# from wine's winnt.h
|
||||||
|
# inheritance AceFlags
|
||||||
|
OBJECT_INHERIT_ACE = 0x01,
|
||||||
|
CONTAINER_INHERIT_ACE = 0x02,
|
||||||
|
NO_PROPAGATE_INHERIT_ACE = 0x04,
|
||||||
|
INHERIT_ONLY_ACE = 0x08,
|
||||||
|
INHERITED_ACE = 0x10,
|
||||||
|
|
||||||
|
# AceFlags mask for what events we (should) audit
|
||||||
|
SUCCESSFUL_ACCESS_ACE_FLAG = 0x40,
|
||||||
|
FAILED_ACCESS_ACE_FLAG = 0x80,
|
||||||
|
),
|
||||||
|
"size" / Int16ul,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
|
||||||
|
AllowDenyBodyStruct = Struct(
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask
|
||||||
|
"access_mask" / Int32ul,
|
||||||
|
"sid" / SIDStruct,
|
||||||
|
)
|
||||||
|
|
||||||
|
ACE_BODY_STRUCTS = {
|
||||||
|
'ACCESS_ALLOWED_ACE_TYPE': AllowDenyBodyStruct,
|
||||||
|
'ACCESS_DENIED_ACE_TYPE': AllowDenyBodyStruct,
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
ACEStruct = WithSize('header.size', Struct(
|
||||||
|
"header" / ACEHeaderStruct,
|
||||||
|
"body" / Switch(
|
||||||
|
this.header.type,
|
||||||
|
ACE_BODY_STRUCTS,
|
||||||
|
),
|
||||||
|
#Padding(lambda this: this.header.size - this._subcons.header.sizeof() - this._subcons.body.sizeof()),
|
||||||
|
))
|
||||||
|
|
||||||
|
ACLStruct = WithSize('header.size', Struct(
|
||||||
|
"header" / ACLHeaderStruct,
|
||||||
|
"items" / ACEStruct[this.header.cnt],
|
||||||
|
#Padding(lambda this: this.header.size - this._subcons.header.sizeof() - this._subcons.items.sizeof()),
|
||||||
|
))
|
||||||
|
|
||||||
|
SecurityDescriptorStruct = Struct(
|
||||||
|
"header" / SDHeaderStruct,
|
||||||
|
"owner_sid" / Pointer(this.header.owner_sid_offset, SIDStruct),
|
||||||
|
"group_sid" / Pointer(this.header.group_sid_offset, SIDStruct),
|
||||||
|
"sacl" / If(lambda this: this.header.flags.get('SE_SACL_PRESENT') and this.header.sacl_offset >= this._subcons.header.sizeof(), Pointer(this.header.sacl_offset, ACLStruct)),
|
||||||
|
"dacl" / If(lambda this: this.header.flags.get('SE_DACL_PRESENT') and this.header.dacl_offset >= this._subcons.header.sizeof(), Pointer(this.header.dacl_offset, ACLStruct)),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Constructable:
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, s):
|
||||||
|
return cls.from_construct(cls.STRUCT.parse(s))
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
return self.STRUCT.build(self.to_construct())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SID(Constructable):
|
||||||
|
STRUCT = SIDStruct
|
||||||
|
def __init__(self, revision, auth=None, *subauth):
|
||||||
|
if isinstance(revision, str):
|
||||||
|
if not revision.startswith('S-'): raise ValueError
|
||||||
|
revision, auth, *subauth = [ int(x) for x in revision[2:].split('-') ]
|
||||||
|
elif isinstance(revision, SID):
|
||||||
|
sid = revision
|
||||||
|
revision = sid.revision
|
||||||
|
auth = sid.auth
|
||||||
|
subauth = sid.subauth
|
||||||
|
if auth is None: raise ValueError
|
||||||
|
self.revision = revision
|
||||||
|
self.auth = auth
|
||||||
|
self.subauth = tuple(subauth)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, SID):
|
||||||
|
return self.revision == other.revision and self.auth == other.auth and self.subauth == other.subauth
|
||||||
|
elif isinstance(other, str):
|
||||||
|
return self == SID(other)
|
||||||
|
else: return NotImplemented
|
||||||
|
|
||||||
|
__req__ = __eq__
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.revision, self.auth, self.subauth))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'S-' + '-'.join( str(a) for a in (self.revision, self.auth)+self.subauth )
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'SID({str(self)!r})'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_construct(cls, cont):
|
||||||
|
return cls(cont.revision, cont.auth, *cont.subauth)
|
||||||
|
|
||||||
|
def to_construct(self):
|
||||||
|
return dict(revision=self.revision, cnt=len(self.subauth), auth=self.auth, subauth=self.subauth)
|
||||||
|
|
||||||
|
SID_ADMINISTRATORS = SID('S-1-5-32-544') # the built-in Administrators group
|
||||||
|
SID_SYSTEM = SID('S-1-5-18') # the Local System (NT AUTHORITY\SYSTEM) account
|
||||||
|
SID_EVERYONE = SID('S-1-1-0')
|
||||||
|
|
||||||
|
# as empiricaly set by Windows 10
|
||||||
|
MASK_FULL_CONTROL = 0x1f01ff
|
||||||
|
MASK_READ_EXECUTE = 0x1200a9
|
||||||
|
|
||||||
|
def flags2set(flags):
|
||||||
|
return frozenset( str(k) for k,v in flags.items() if v if k != '_flagsenum' )
|
||||||
|
|
||||||
|
def set2flags(s):
|
||||||
|
return Container({ f: True for f in set(s) })
|
||||||
|
|
||||||
|
class ACE(Constructable):
|
||||||
|
STRUCT = ACEStruct
|
||||||
|
ALLOW = 'ACCESS_ALLOWED_ACE_TYPE'
|
||||||
|
DENY = 'ACCESS_DENIED_ACE_TYPE'
|
||||||
|
def __init__(self, type, access_mask, sid, flags=None, inheritance=None):
|
||||||
|
self.type = type
|
||||||
|
self.access_mask = access_mask
|
||||||
|
self.sid = sid
|
||||||
|
if flags is None and inheritance is None:
|
||||||
|
inheritance = 'all'
|
||||||
|
flags = set(flags or [])
|
||||||
|
if inheritance in ('object', 'file', 'all'):
|
||||||
|
flags |= {'OBJECT_INHERIT_ACE'}
|
||||||
|
if inheritance in ('container', 'dir', 'all'):
|
||||||
|
flags |= {'CONTAINER_INHERIT_ACE'}
|
||||||
|
self.flags = frozenset(flags)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'ACE({self.type!r}, {self.access_mask!r}, {self.sid!r}, flags={set(self.flags)!r})'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_construct(cls, cont):
|
||||||
|
if cont.header.type not in (cls.ALLOW, cls.DENY):
|
||||||
|
raise NotImplementedError
|
||||||
|
return ACE(str(cont.header.type), cont.body.access_mask, SID.from_construct(cont.body.sid), flags=flags2set(cont.header.flags))
|
||||||
|
|
||||||
|
def to_construct(self):
|
||||||
|
r = Container()
|
||||||
|
r.header = Container(type=self.type, size=0, flags=set2flags(self.flags))
|
||||||
|
r.body = Container(access_mask=self.access_mask, sid=self.sid.to_construct(), flags={ f: True for f in self.flags })
|
||||||
|
return r
|
||||||
|
|
||||||
|
def make_inherited(self):
|
||||||
|
return ACE(self.type, self.access_mask, self.sid, self.flags | {"INHERITED_ACE"} - {"INHERIT_ONLY_ACE"})
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityDescriptor(Constructable):
|
||||||
|
STRUCT = SecurityDescriptorStruct
|
||||||
|
|
||||||
|
def __init__(self, *, owner_sid=SID_ADMINISTRATORS, group_sid=SID_ADMINISTRATORS, dacl=[], sacl=None, sacl_inherit=True, dacl_inherit=True):
|
||||||
|
self.owner_sid = SID(owner_sid)
|
||||||
|
self.group_sid = SID(group_sid)
|
||||||
|
self.sacl = sacl
|
||||||
|
self.dacl = dacl
|
||||||
|
self.sacl_inherit = sacl_inherit
|
||||||
|
self.dacl_inherit = dacl_inherit
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
r = ['SecurityDescriptor(']
|
||||||
|
r.append(f' owner_sid={self.owner_sid!r},')
|
||||||
|
r.append(f' group_sid={self.group_sid!r},')
|
||||||
|
for name in ('sacl', 'dacl'):
|
||||||
|
acl = getattr(self, name)
|
||||||
|
if not acl: continue
|
||||||
|
r.append(f' {name}=[')
|
||||||
|
for ace in acl:
|
||||||
|
r.append(' '*8 + repr(ace))
|
||||||
|
r.append(' ],')
|
||||||
|
if not self.sacl_inherit: r.append(' sacl_inherit=False,')
|
||||||
|
if not self.dacl_inherit: r.append(' dacl_inherit=False,')
|
||||||
|
r.append(')')
|
||||||
|
return '\n'.join(r)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def acl_from_construct(cls, cont):
|
||||||
|
if cont: return [ ACE.from_construct(itm) for itm in cont['items'] ]
|
||||||
|
else: return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def acl_to_construct(cls, acl):
|
||||||
|
# Empty ACL is not the same as no ACL.
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/null-dacls-and-empty-dacls
|
||||||
|
if acl is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
r = Container()
|
||||||
|
r.header = Container(revision=2, cnt=len(acl), size=0)
|
||||||
|
r.items = [ itm.to_construct() for itm in acl ]
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_construct(cls, cont):
|
||||||
|
owner_sid = SID.from_construct(cont.owner_sid)
|
||||||
|
group_sid = SID.from_construct(cont.group_sid)
|
||||||
|
sacl = cls.acl_from_construct(cont.sacl)
|
||||||
|
dacl = cls.acl_from_construct(cont.dacl)
|
||||||
|
flags = flags2set(cont.header.flags)
|
||||||
|
return SecurityDescriptor(owner_sid=owner_sid, group_sid=group_sid, sacl=sacl, dacl=dacl,
|
||||||
|
sacl_inherit="SE_SACL_PROTECTED" not in flags,
|
||||||
|
dacl_inherit="SE_DACL_PROTECTED" not in flags,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_construct(self):
|
||||||
|
hdr = Container()
|
||||||
|
r = Container()
|
||||||
|
r.header = hdr
|
||||||
|
r.owner_sid = self.owner_sid.to_construct()
|
||||||
|
r.group_sid = self.group_sid.to_construct()
|
||||||
|
r.dacl = self.acl_to_construct(self.dacl)
|
||||||
|
r.sacl = self.acl_to_construct(self.sacl)
|
||||||
|
|
||||||
|
# SE_SELF_RELATIVE specifies that offsets in header are relative to start of SD.
|
||||||
|
# This should be always set in on-disk SDs.
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/secauthz/absolute-and-self-relative-security-descriptors
|
||||||
|
flags = {"SE_SELF_RELATIVE"}
|
||||||
|
# Windows seems to always set SE_DACL_AUTO_INHERITED but not SE_DACL_AUTO_INHERIT_REQ. We mirror that.
|
||||||
|
if self.sacl is not None: flags |= {"SE_SACL_PRESENT", "SE_SACL_AUTO_INHERITED"}
|
||||||
|
if self.dacl is not None: flags |= {"SE_DACL_PRESENT", "SE_DACL_AUTO_INHERITED"}
|
||||||
|
if not self.sacl_inherit:
|
||||||
|
flags |= {"SE_SACL_PROTECTED"}
|
||||||
|
if not self.dacl_inherit:
|
||||||
|
flags |= {"SE_DACL_PROTECTED"}
|
||||||
|
|
||||||
|
hdr.flags = set2flags(flags)
|
||||||
|
|
||||||
|
sacl_size = len(ACLStruct.build(r.sacl)) if r.sacl else 0
|
||||||
|
dacl_size = len(ACLStruct.build(r.dacl)) if r.dacl else 0
|
||||||
|
pos = SDHeaderStruct.sizeof()
|
||||||
|
def add_element(struct, cont):
|
||||||
|
nonlocal pos
|
||||||
|
off = pos
|
||||||
|
pos += len(struct.build(cont))
|
||||||
|
return off
|
||||||
|
hdr.owner_sid_offset = add_element(SIDStruct, r.owner_sid)
|
||||||
|
hdr.group_sid_offset = add_element(SIDStruct, r.group_sid)
|
||||||
|
if r.sacl: hdr.sacl_offset = add_element(ACLStruct, r.sacl)
|
||||||
|
else: hdr.sacl_offset = 0
|
||||||
|
if r.dacl: hdr.dacl_offset = add_element(ACLStruct, r.dacl)
|
||||||
|
else: hdr.dacl_offset = 0
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def apply_sd_recursively(path, sd=None, *, dacl=None, skip_protected=True, set_owner=False, set_group=False):
|
||||||
|
path = Path(path)
|
||||||
|
if sd is None:
|
||||||
|
sd = SecurityDescriptor.parse(dump_sd(path))
|
||||||
|
else:
|
||||||
|
write_sd(path, sd)
|
||||||
|
|
||||||
|
def visit(p):
|
||||||
|
is_dir = p.is_dir()
|
||||||
|
kind = 'CONTAINER' if is_dir else 'OBJECT'
|
||||||
|
acl = [ ace.make_inherited() for ace in sd.dacl if f'{kind}_INHERIT_ACE' in ace.flags ]
|
||||||
|
child_sd = SecurityDescriptor.parse(dump_sd(p))
|
||||||
|
if (not child_sd.dacl_inherit) and skip_protected: return # do not modify ACL and do not recurse
|
||||||
|
child_sd.dacl = acl
|
||||||
|
if set_owner: child_sd.owner_sid = sd.owner_sid
|
||||||
|
if set_group: child_sd.group_sid = sd.group_sid
|
||||||
|
print(p, child_sd)
|
||||||
|
write_sd(p, child_sd)
|
||||||
|
if is_dir:
|
||||||
|
for child in p.iterdir(): visit(child)
|
||||||
|
|
||||||
|
for p in path.iterdir(): visit(p)
|
||||||
|
|
||||||
|
|
||||||
|
|
24
postproc/openssh-server/openssh_acl.py
Executable file
24
postproc/openssh-server/openssh_acl.py
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import sys,os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||||
|
print(sys.path)
|
||||||
|
|
||||||
|
from ntfs_acl import *
|
||||||
|
|
||||||
|
|
||||||
|
mnt = Path(sys.argv[1])
|
||||||
|
openssh_dir = mnt / "Program Files/OpenSSH"
|
||||||
|
|
||||||
|
|
||||||
|
# Make sure only Administrators and SYSTEM can write to OpenSSH install dir
|
||||||
|
apply_sd_recursively(
|
||||||
|
openssh_dir,
|
||||||
|
SecurityDescriptor(dacl=[
|
||||||
|
ACE(ACE.ALLOW, MASK_FULL_CONTROL, SID_SYSTEM),
|
||||||
|
ACE(ACE.ALLOW, MASK_FULL_CONTROL, SID_ADMINISTRATORS) ,
|
||||||
|
ACE(ACE.ALLOW, MASK_READ_EXECUTE, SID_EVERYONE)
|
||||||
|
], dacl_inherit=False)
|
||||||
|
)
|
18
postproc/openssh-server/setup.sh
Executable file
18
postproc/openssh-server/setup.sh
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mnt="$(readlink -f "$1")"
|
||||||
|
|
||||||
|
my_dir="$(dirname "$0")"
|
||||||
|
cd "$my_dir"
|
||||||
|
|
||||||
|
if [[ ! -e OpenSSH-Win64 ]]; then
|
||||||
|
# adapted from https://github.com/PowerShell/Win32-OpenSSH/wiki/How-to-retrieve-links-to-latest-packages
|
||||||
|
url="$(curl -v https://github.com/PowerShell/Win32-OpenSSH/releases/latest/ 2>&1 |grep -i '< location:' | awk '{print $3}' |tr -d '\r\n'|sed -re 's/tag/download/')/OpenSSH-Win64.zip"
|
||||||
|
curl "$url" -Lfo OpenSSH-Win64.zip
|
||||||
|
unzip OpenSSH-Win64
|
||||||
|
fi
|
||||||
|
cp -rT OpenSSH-Win64 "$mnt/Program Files/OpenSSH"
|
||||||
|
|
||||||
|
hivexregedit --merge --prefix 'HKEY_LOCAL_MACHINE\SYSTEM' "$mnt"/Windows/System32/config/SYSTEM "$my_dir/sshd_service.reg"
|
||||||
|
"$my_dir/openssh_acl.py" "$mnt"
|
||||||
|
|
20
postproc/openssh-server/sshd_service.reg
Normal file
20
postproc/openssh-server/sshd_service.reg
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\sshd]
|
||||||
|
"Description"="SSH protocol based service to provide secure encrypted communications between two untrusted hosts over an insecure network."
|
||||||
|
"DisplayName"="OpenSSH SSH Server"
|
||||||
|
"ErrorControl"=dword:00000001
|
||||||
|
"ImagePath"="C:\\Program Files\\OpenSSH\\sshd.exe"
|
||||||
|
"ObjectName"="LocalSystem"
|
||||||
|
"RequiredPrivileges"=hex(7):53,00,65,00,41,00,73,00,73,00,69,00,67,00,6e,00,50,\
|
||||||
|
00,72,00,69,00,6d,00,61,00,72,00,79,00,54,00,6f,00,6b,00,65,00,6e,00,50,00,\
|
||||||
|
72,00,69,00,76,00,69,00,6c,00,65,00,67,00,65,00,00,00,53,00,65,00,54,00,63,\
|
||||||
|
00,62,00,50,00,72,00,69,00,76,00,69,00,6c,00,65,00,67,00,65,00,00,00,53,00,\
|
||||||
|
65,00,42,00,61,00,63,00,6b,00,75,00,70,00,50,00,72,00,69,00,76,00,69,00,6c,\
|
||||||
|
00,65,00,67,00,65,00,00,00,53,00,65,00,52,00,65,00,73,00,74,00,6f,00,72,00,\
|
||||||
|
65,00,50,00,72,00,69,00,76,00,69,00,6c,00,65,00,67,00,65,00,00,00,53,00,65,\
|
||||||
|
00,49,00,6d,00,70,00,65,00,72,00,73,00,6f,00,6e,00,61,00,74,00,65,00,50,00,\
|
||||||
|
72,00,69,00,76,00,69,00,6c,00,65,00,67,00,65,00,00,00,00,00
|
||||||
|
"Start"=dword:00000002
|
||||||
|
"Type"=dword:00000010
|
||||||
|
|
@ -12,6 +12,11 @@ import subprocess
|
|||||||
import tempfile
|
import tempfile
|
||||||
import parted
|
import parted
|
||||||
|
|
||||||
|
my_dir = Path(__file__).parent
|
||||||
|
if str(my_dir) not in sys.path:
|
||||||
|
# allow postprocess scripts to import our python modules, especially ntfs_acl
|
||||||
|
os.environ['PYTHONPATH'] = f"{my_dir}:{os.environ.get('PYTHONPATH','')}"
|
||||||
|
|
||||||
def is_part(pth):
|
def is_part(pth):
|
||||||
pth = Path(pth)
|
pth = Path(pth)
|
||||||
if not pth.is_block_device(): raise RuntimeError("Not a block device, cannot determine partition-ness")
|
if not pth.is_block_device(): raise RuntimeError("Not a block device, cannot determine partition-ness")
|
||||||
@ -122,41 +127,48 @@ def copy_boot_files(dir):
|
|||||||
shutil.copy(Path(__file__).parent / 'BCD', ci_lookup(boot_dir, 'BCD', creating=True))
|
shutil.copy(Path(__file__).parent / 'BCD', ci_lookup(boot_dir, 'BCD', creating=True))
|
||||||
|
|
||||||
|
|
||||||
def setup_part(part, wim, image_name, *, unattend=None, postproc=None):
|
def setup_part(part, wim, image_name, *, unattend=None, postproc=None, postproc_only=False):
|
||||||
format_part(part)
|
if not postproc_only:
|
||||||
|
format_part(part)
|
||||||
|
apply_wim(part, wim, image_name)
|
||||||
setup_vbr(part)
|
setup_vbr(part)
|
||||||
apply_wim(part, wim, image_name)
|
|
||||||
with with_mounted(part) as dir:
|
with with_mounted(part) as dir:
|
||||||
copy_boot_files(dir)
|
copy_boot_files(dir)
|
||||||
if unattend:
|
if unattend:
|
||||||
trg = ci_lookup(dir, 'Windows', 'Panther', 'unattend.xml', creating=True, parents=True)
|
trg = ci_lookup(dir, 'Windows', 'Panther', 'unattend.xml', creating=True, parents=True)
|
||||||
print(f"Copying unattend file: {unattend} -> {trg}")
|
print(f"Copying unattend file: {unattend} -> {trg}")
|
||||||
shutil.copy(unattend, trg)
|
shutil.copy(unattend, trg)
|
||||||
if postproc:
|
for script in postproc:
|
||||||
if '/' not in postproc: postproc = f"./{postproc}"
|
script = str(script)
|
||||||
subprocess.run([str(postproc), dir])
|
if '/' not in script: script = f"./{script}"
|
||||||
|
print("Running script", script, file=sys.stderr)
|
||||||
|
subprocess.run([str(script), dir], check=True)
|
||||||
|
|
||||||
|
|
||||||
def exactly_one(*a):
|
def exactly_one(*a):
|
||||||
return sum( bool(x) for x in a ) == 1
|
return sum( bool(x) for x in a ) == 1
|
||||||
|
|
||||||
def main(*, disk=None, part=None, wim=None, iso=None, image_name=None, unattend=None, postproc=None):
|
def main(*, disk=None, part=None, wim=None, iso=None, image_name=None, unattend=None,
|
||||||
|
postproc:(str,clize.parameters.multi())=[], openssh_server=False,
|
||||||
|
postproc_only=False):
|
||||||
if not exactly_one(disk, part):
|
if not exactly_one(disk, part):
|
||||||
raise ArgumentError("You must specify exactly one of 'disk', 'part'")
|
raise ArgumentError("You must specify exactly one of 'disk', 'part'")
|
||||||
if not exactly_one(wim, iso):
|
if not (exactly_one(wim, iso) or postproc_only):
|
||||||
raise ArgumentError("You must specify exactly one of 'wim', 'iso'")
|
raise ArgumentError("You must specify exactly one of 'wim', 'iso'")
|
||||||
|
if openssh_server:
|
||||||
|
postproc.append(my_dir / 'postproc/openssh-server/setup.sh')
|
||||||
with ExitStack() as es:
|
with ExitStack() as es:
|
||||||
if iso:
|
if iso:
|
||||||
wim = es.enter_context(with_iso(iso))
|
wim = es.enter_context(with_iso(iso))
|
||||||
if disk:
|
if disk:
|
||||||
create_partitions(disk)
|
if not postproc_only: create_partitions(disk)
|
||||||
with with_device(disk) as dev:
|
with with_device(disk) as dev:
|
||||||
create_partitions(dev)
|
#create_partitions(dev)
|
||||||
setup_mbr(dev)
|
if not postproc_only: setup_mbr(dev)
|
||||||
part = part_path(dev, 1)
|
part = part_path(dev, 1)
|
||||||
setup_part(part, wim, image_name, unattend=unattend, postproc=postproc)
|
setup_part(part, wim, image_name, unattend=unattend, postproc=postproc, postproc_only=postproc_only)
|
||||||
else:
|
else:
|
||||||
setup_part(part, unattend=unattend, postproc=postproc)
|
setup_part(part, unattend=unattend, postproc=postproc, postproc_only=postproc_only)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
clize.run(main)
|
clize.run(main)
|
||||||
|
Loading…
Reference in New Issue
Block a user