1
0
mirror of https://github.com/altlinux/gpupdate.git synced 2025-01-18 02:04:48 +03:00

Initial release

This commit is contained in:
Игорь Чудов 2019-11-14 10:32:45 +04:00
commit 66f5b751b6
Signed by untrusted user: nir
GPG Key ID: 0F3883600CAE7AAC
27 changed files with 1153 additions and 0 deletions

2
.gear/rules Normal file
View File

@ -0,0 +1,2 @@
tar: .

0
gpoa/__init__.py Normal file
View File

27
gpoa/control/__init__.py Normal file
View File

@ -0,0 +1,27 @@
import subprocess
import threading
class control:
def __init__(self, name, value):
self.control_name = name
self.control_value = value
self.possible_values = self._query_control_values()
def _query_control_values(self):
proc = subprocess.Popen(['sudo', 'control', self.control_name, 'list'], stdout=subprocess.PIPE)
for line in proc.stdout:
values = line.split()
return values
def get_control_name(self):
return self.control_name
def get_control_status(self):
proc = subprocess.Popen(['sudo', 'control', self.control_name], stdout=subprocess.PIPE)
for line in proc.stdout:
return line.rstrip('\n\r')
def set_control_status(self):
print('Setting control {} to {}'.format(self.control_name, self.control_value))
#proc = subprocess.Popen(['sudo', 'control', self.control_name, status], stdout=subprocess.PIPE)

2
gpoa/generator/gen.py Normal file
View File

@ -0,0 +1,2 @@
if __name__ == "__main__":
print("main here!")

View File

@ -0,0 +1 @@
Jinja2

339
gpoa/main.py Executable file
View File

@ -0,0 +1,339 @@
#! /usr/bin/env python3
import argparse
# Facility to determine GPTs for user
import optparse
from samba import getopt as options
from samba.gpclass import get_dc_hostname, gpo_version, check_safe_path
import samba.gpo
# Primitives to work with libregistry
# samba.registry.str_regtype(2) -> 'REG_EXPAND_SZ'
# Taken from python/samba/tests/registry.py
from samba import registry
# PReg object generator
from samba.dcerpc import preg
from samba.dcerpc import misc
import samba.ndr
from samba.gp_parse.gp_pol import GPPolParser
# This is needed by Registry.pol file search
import os
import re
# This is needed for merging lists of PReg files.
import itertools
# Our native control facility
import control
class applier_backend:
def __init__(self):
pass
class hreg_filesystem_backend(applier_backend):
_gpupdate_cache = '/var/cache/gpupdate'
_base_path = '/tmp/gpoa_scripts'
def __init__(self, sid):
self._sid = sid
self._root_path = '{base}/root'.format(base=self._base_path)
self._user_path = '{base}/user'.format(base=self._base_path)
def _get_files(self, dir_path):
'''
List all files located in hreg cache
'''
files = list()
for entry in os.listdir(dir_path):
abspath = os.path.join(dir_path, entry)
try:
if os.path.isdir(abspath):
files.append(self._get_files(abspath))
else:
files.append(abspath)
except:
pass
return files
def _read_value(self, path):
'''
Read hreg-cached value from file and return object representing it.
'''
pass
def get_values(self):
cpath = os.path.join(self._gpupdate_cache, self._sid)
entry_list = self._get_files(cpath)
for entry in entry_list:
val = self._read_value(entry)
class samba_backend(applier_backend):
_samba_registry_file = '/var/cache/samba/registry.ldb'
_mahine_hive = 'HKEY_LOCAL_MACHINE'
_user_hive = 'HKEY_CURRENT_USER'
_machine_pol_path_pattern = '[Mm][Aa][Cc][Hh][Ii][Nn][Ee]'
_user_pol_path_pattern = '[Uu][Ss][Ee][Rr]'
def _check_sysvol_present(self, gpo):
'''
Check if there is SYSVOL path for GPO assigned
'''
if not gpo.file_sys_path:
print('No SYSVOL entry assigned to GPO {}'.format(gpo.name))
return False
return True
def _gpo_get_gpt_polfiles(self, gpo_obj):
'''
Find absolute path to cached GPT directory and return
dict of lists with PReg file paths.
'''
if self._check_sysvol_present(gpo_obj):
print('Found SYSVOL entry {} for GPO {}'.format(gpo_obj.file_sys_path, gpo_obj.name))
path = check_safe_path(gpo_obj.file_sys_path).upper()
gpt_abspath = os.path.join(self.cache_dir, 'gpo_cache', path)
print('Path: {}'.format(path))
policy_files = self._find_regpol_files(gpt_abspath)
return policy_files
return dict({ 'machine_regpols': [], 'user_regpols': [] })
def __init__(self, loadparm, creds, sid, dc):
# Regular expressions to split PReg files into user and machine parts
self._machine_pol_path_regex = re.compile(self._machine_pol_path_pattern)
self._user_pol_path_regex = re.compile(self._user_pol_path_pattern)
# User SID to work with HKCU hive
self.sid = sid
# Look at python-samba tests for code examples
self.registry = registry.Registry()
self.machine_hive = registry.open_ldb('/tmp/machine_hive.ldb')
self.user_hive = registry.open_ldb('/tmp/HKCU-{}.ldb'.format(self.sid))
self.registry.mount_hive(self.machine_hive, samba.registry.HKEY_LOCAL_MACHINE)
self.registry.mount_hive(self.user_hive, samba.registry.HKEY_CURRENT_USER)
# Samba objects - LoadParm() and CredentialsOptions()
self.loadparm = loadparm
self.creds = creds
self.cache_dir = self.loadparm.get('cache directory')
print('Cache directory is: {}'.format(self.cache_dir))
gpos = get_gpo_list(dc, self.creds, self.loadparm, 'administrator')
self.policy_files = dict({ 'machine_regpols': [], 'user_regpols': [] })
for gpo in gpos:
polfiles = self._gpo_get_gpt_polfiles(gpo)
self.policy_files['machine_regpols'] += polfiles['machine_regpols']
self.policy_files['user_regpols'] += polfiles['user_regpols']
print('Policy files: {}'.format(self.policy_files))
def _parse_pol_file(self, polfile):
'''
Parse PReg file and return its preg object
'''
gpparser = GPPolParser()
data = None
with open(polfile, 'rb') as f:
data = f.read()
gpparser.parse(data)
#print(gpparser.pol_file.__ndr_print__())
return gpparser.pol_file
def _merge_entry(self, hive, entry):
'''
Write preg.entry to hive
'''
# Build hive key path from PReg's key name and value name.
hive_key = '{}\\{}'.format(entry.keyname, entry.valuename)
print('Merging {}'.format(hive_key))
hive.set_value(hive_key, entry.type, entry.data.to_bytes(4, byteorder='big'))
# Dump data to disk
hive.flush()
def get_values(self):
'''
Read data from PReg file and return list of NDR objects (samba.preg)
'''
# FIXME: Return registry and hives instead of samba.preg objects.
preg_objs = []
print('Parsing machine regpols')
for regpol in self.policy_files['machine_regpols']:
print('Processing {}'.format(regpol))
pregfile = self._parse_pol_file(regpol)
preg_objs.append(pregfile)
for entry in pregfile.entries:
self._merge_entry(self.machine_hive, entry)
return preg_objs
def _find_regpol_files(self, gpt_path):
'''
Seek through given GPT directory absolute path and return the dictionary
of user's and machine's Registry.pol files.
'''
print('Finding regpols in: {}'.format(gpt_path))
polfiles = dict({ 'machine_regpols': [], 'user_regpols': [] })
for root, dirs, files in os.walk(gpt_path):
for gpt_file in files:
if gpt_file.endswith('.pol'):
regpol_abspath = os.path.join(root, gpt_file)
if self._machine_pol_path_regex.search(regpol_abspath):
polfiles['machine_regpols'].append(regpol_abspath)
else:
polfiles['user_regpols'].append(regpol_abspath)
print('Polfiles: {}'.format(polfiles))
return polfiles
class applier_frontend:
def __init__(self, regobj):
pass
def apply(self):
pass
class control_applier(applier_frontend):
_registry_branch = 'Software\\BaseALT\\Policies\\Control'
def __init__(self, polfiles):
self.polparsers = polfiles
self.control_settings = self._get_controls(self.polparsers)
self.controls = []
for setting in self.control_settings:
self.controls.append(control.control(setting.valuename, setting.data))
#for e in polfile.pol_file.entries:
# print('{}:{}:{}:{}:{}'.format(e.type, e.data, e.valuename, e.keyname))
def _get_controls(self, polfiles):
'''
Extract control entries from PReg file
'''
controls = []
for parser in polfiles:
for entry in parser.entries:
if entry.keyname == self._registry_branch:
controls.append(entry)
print('Found control setting: {}'.format(entry.valuename))
else:
# Property names are taken from python/samba/gp_parse/gp_pol.py
print('Dropped control setting: {}\\{}'.format(entry.keyname, entry.valuename))
return controls
def apply(self):
'''
Trigger control facility invocation.
'''
for control in self.controls:
control.set_control_status()
def dump_settings(self):
'''
Write actual controls as XML and PReg files
'''
print('Dumping...')
polfile = preg.file()
polfile.header.signature = 'PReg'
polfile.header.version = 1
polfile.num_entries = len(self.control_settings)
polfile.entries = self.control_settings
print(polfile.__ndr_print__())
policy_writer = GPPolParser()
policy_writer.pol_file = polfile
policy_writer.write_xml('test_reg.xml')
policy_writer.write_binary('test_reg.pol')
class applier:
def __init__(self, backend):
self.backend = backend
self.gpvalues = self.load_values()
print('Values: {}'.format(self.gpvalues))
capplier = control_applier(self.gpvalues)
self.appliers = dict({ 'control': capplier })
def load_values(self):
'''
This thing returns the list of samba.preg objects for
now but it must be transformed to return registry and
its hives to read values from.
'''
print('Get values from backend')
return self.backend.get_values()
def apply_parameters(self):
print('Applying')
self.appliers['control'].apply()
# This thing dumps Registry.pol files to disk from data structures
#print('Writing settings to file')
#self.appliers['control'].dump_settings()
def parse_arguments():
arguments = argparse.ArgumentParser(description='Generate configuration out of parsed policies')
arguments.add_argument('sid',
type=str,
help='SID to parse policies from')
arguments.add_argument('--dc',
type=str,
help='FQDN of the domain to replicate SYSVOL from')
return arguments.parse_args()
def get_gpo_list(dc_hostname, creds, lp, user):
gpos = []
ads = samba.gpo.ADS_STRUCT(dc_hostname, lp, creds)
if ads.connect():
#gpos = ads.get_gpo_list(creds.get_username())
gpos = ads.get_gpo_list(user)
print('Got GPO list:')
for gpo in gpos:
# These setters are taken from libgpo/pygpo.c
# print(gpo.ds_path) # LDAP entry
print('{} ({})'.format(gpo.display_name, gpo.name))
print('------')
return gpos
def get_machine_domain():
pass
def select_dc(lp, creds, dc):
samba_dc = get_dc_hostname(creds, lp)
if samba_dc != dc and dc != None:
print('Samba DC setting is {} and is overwritten by user setting {}'.format(samba_dc, dc))
return dc
return samba_dc
def main():
args = parse_arguments()
#back = hreg_filesystem_backend(args.sid)
parser = optparse.OptionParser('GPO Applier')
sambaopts = options.SambaOptions(parser)
credopts = options.CredentialsOptions(parser)
# Initialize loadparm context
lp = sambaopts.get_loadparm()
creds = credopts.get_credentials(lp, fallback_machine=True)
# Determine the default Samba DC for replication and try
# to overwrite it with user setting.
dc = select_dc(lp, creds, args.dc)
back = samba_backend(lp, creds, args.sid, dc)
appl = applier(back)
appl.apply_parameters()
if __name__ == "__main__":
main()

13
gpoa/policy/__init__.py Normal file
View File

@ -0,0 +1,13 @@
import policy.firewall
import policy.removable_devices_perms
import policy.printers
import policy.shortcuts
import policy.applications
from policy.common import Perms
data_roots = {}
data_roots.update(policy.firewall.data_roots)
data_roots.update(policy.removable_devices_perms.data_roots)
data_roots.update(policy.printers.data_roots)
data_roots.update(policy.shortcuts.data_roots)
data_roots.update(policy.applications.data_roots)

View File

@ -0,0 +1,24 @@
from policy.common import Policy, Perms
import os
from pathlib import Path
class Applications (Policy):
def __init__(self):
self._Policy__name = "Applications"
self._Policy__script_name = "applications.sh"
self._Policy__template = "applications.bash.j2"
self._Policy__perms = Perms.ROOT
def process(self, scope, path):
applications = []
print("{name} processing {path} ({scope})".format(name=self.name,path=path,scope=scope))
for p in os.listdir(path):
package_name = Path('{}/{}/packageName'.format(path, p)).read_text()
product_name = Path('{}/{}/productName'.format(path, p)).read_text()
applications.append({'uuid': p, 'package': package_name, 'name': product_name})
return ({'applications': applications})
data_roots = {
"Applications": Applications
}

44
gpoa/policy/common.py Normal file
View File

@ -0,0 +1,44 @@
from enum import Enum
class Perms (Enum):
USER = 1
ROOT = 2
class Policy (object):
@property
def name(self):
try:
return self.__name
except AttributeError as e:
raise e
@property
def script_name(self):
try:
return self.__script_name
except AttributeError as e:
raise e
@property
def template(self):
try:
return self.__template
except AttributeError as e:
raise e
@property
def data_roots(self):
try:
return self.__data_roots
except AttributeError as e:
raise e
@property
def perms(self):
try:
return self.__perms
except AttributeError as e:
raise e
def process(self, scope, path):
raise "process must be implemented in child class"

46
gpoa/policy/firewall.py Normal file
View File

@ -0,0 +1,46 @@
from policy.common import Policy, Perms
import os
from pathlib import Path
class Firewall (Policy):
def __init__(self):
self._Policy__name = "Firewall"
self._Policy__script_name = "firewall.sh"
self._Policy__template = "firewall.bash.j2"
self._Policy__perms = Perms.ROOT
def process(self, scope, path):
print("{name} processing {path} ({scope})".format(name=self.name,path=path,scope=scope))
zoneRules = {}
for p in os.listdir(path):
if path.split('/')[-1:] == ["WindowsFirewall"]:
for zone in os.listdir(path):
print("processing {} prifile".format(zone))
zoneRules[zone] = self.__process_firewall_rules(zone, path)
return ({'zoneRules': zoneRules})
def __process_firewall_rules(self, zone, path):
print("processing rules in {} zone".format(zone))
base = "{}/{}".format(path, zone)
openPortsPath = "GloballyOpenPorts"
rules = {}
if os.path.exists("{}/{}".format(base, openPortsPath)):
listPath = "{}/{}/List".format(base, openPortsPath)
openPorts = []
for r in os.listdir(listPath):
rule = Path('{}/{}'.format(listPath, r)).read_text()
[port, proto, srcs, enabled, name] = rule.split(':')
sources = srcs.split(',')
openPorts.append({'proto': proto, 'port': port, 'sources': sources})
rules['openPorts'] = openPorts
return(rules)
# for r in os.listdir("{}/{}".format(path,zone)):
# print(r)
data_roots = {
"Software/Microsoft/Windows/CurrentVersion/Policies/NetworkAccessProtection/ClientConfig": Firewall,
"Software/Microsoft/Windows/CurrentVersion/Policies/WindowsFirewall": Firewall
}

40
gpoa/policy/printers.py Normal file
View File

@ -0,0 +1,40 @@
from policy.common import Policy, Perms
import os
class Printers (Policy):
def __init__(self):
self._Policy__name = "Printers"
self._Policy__script_name = "printers.sh"
self._Policy__template = "printers.bash.j2"
self._Policy__perms = Perms.ROOT
def process(self, scope, path):
print("{name} processing {path} ({scope})".format(name=self.name,path=path,scope=scope))
shared_path = "{}/SharedPrinter".format(path)
port_path = "{}/PortPrinter".format(path)
local_path = "{}/LocalPrinter".format(path)
printers = []
if os.path.exists(shared_path):
print("SharedPrinters")
if os.path.exists(port_path):
print("PortPrinters")
for p in os.listdir(port_path):
with open("{}/{}/ipAddress".format(port_path,p)) as f:
addr = f.read()
with open("{}/{}/localName".format(port_path,p)) as f:
name = f.read()
prns = {'name': name, 'addr': addr}
printers.append(prns)
if os.path.exists(local_path):
print("LocalPrinters")
return ({'printers': printers})
@property
def data_roots(self):
return data_roots
data_roots = {
"Preferences/Printers": Printers
}

View File

@ -0,0 +1,51 @@
from policy.common import Policy, Perms
from enum import Enum
import os
class DevType (Enum):
CDDVD = "{53f56308-b6bf-11d0-94f2-00a0c91efb8b}"
FLOPPY = "{53f56311-b6bf-11d0-94f2-00a0c91efb8b}"
REMDISKS = "{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}"
TAPEDRIVE = "{53f5630b-b6bf-11d0-94f2-00a0c91efb8b}"
WPDDEV1 = "{6AC27878-A6FA-4155-BA85-F98F491D4F33}"
WPDDEV2 = "{F33FDC04-D1AC-4E8E-9A30-19BBD4B108AE}"
class RemovableDevices (Policy):
def __init__(self):
self._Policy__name = "RemovebleDivicesPerms"
self._Policy__script_name = "removable_devices_perms.sh"
self._Policy__template = "removable_devices_perms.bash.j2"
self._Policy__perms = Perms.ROOT
def process(self, scope, path):
print("{name} processing {path} ({scope})".format(name=self.name,path=path,scope=scope))
perms = {}
for p in os.listdir(path):
if DevType(p) == DevType.REMDISKS:
perms['Removable'] = self.__parse_perms('{}/{}'.format(path,p))
# elif DevType(p) == DevType.CDDVD:
perms['CDDVD'] = {'deny_exec': True, 'deny_read': False, 'deny_write': True}
return (perms)
def __parse_perms(self, path):
deny_exec = self.__read_perm('{}/Deny_Execute.dword'.format(path))
deny_read = self.__read_perm('{}/Deny_Read.dword'.format(path))
deny_write = self.__read_perm('{}/Deny_Write.dword'.format(path))
return ({'deny_exec': deny_exec, 'deny_read': deny_read, 'deny_write': deny_write})
def __read_perm(self, path):
try:
f = open(path, 'r')
except FileNotFoundError:
return False
p = f.read()
if p == "0x00000001":
return True
else:
return False
data_roots = {
"Software/Microsoft/Windows/CurrentVersion/Policies/RemovableStorageDevices": RemovableDevices
}

24
gpoa/policy/shortcuts.py Normal file
View File

@ -0,0 +1,24 @@
from policy.common import Policy, Perms
import os
from pathlib import Path
class Shortcuts (Policy):
def __init__(self):
self._Policy__name = "Shortcuts"
self._Policy__script_name = "shortcuts.sh"
self._Policy__template = "shortcuts.bash.j2"
self._Policy__perms = Perms.USER
def process(self, scope, path):
shortcuts = []
print("{name} processing {path} ({scope})".format(name=self.name,path=path,scope=scope))
for p in os.listdir(path):
target_path = Path('{}/{}/targetPath'.format(path, p)).read_text()
arguments = Path('{}/{}/arguments'.format(path, p)).read_text()
shortcuts.append({'name': p, 'target': target_path, 'args': arguments})
return ({'shortcuts': shortcuts})
data_roots = {
"Preferences/Shortcuts": Shortcuts
}

7
gpoa/templ.py Normal file
View File

@ -0,0 +1,7 @@
from jinja2 import Environment, PackageLoader, FileSystemLoader, StrictUndefined, select_autoescape
templateLoader = FileSystemLoader(searchpath="./templates")
env = Environment(loader=templateLoader, undefined=StrictUndefined)
template = env.get_template('printers.bash.j2')
print(template.render(printer_name="HP_via_script", printer_address="10.64.128.250", DEBUG=False))

View File

@ -0,0 +1,9 @@
{% include "header.bash.j2" %}
MODULE_NAME="Applications"
{% for a in applications %}
{%set app_name = a.name %}
{%set pkg_name = a.package %}
logI "Install {{ pkg_name }} via apt-get"
apt-get install -y "${pkg_name}"
{% endfor %}

View File

@ -0,0 +1,22 @@
{% include 'header.bash.j2' %}
MODULE_NAME="Firewall"
on_exit() {
logI "on_exit in ${MODULE_NAME}"
}
{% for z,rs in zoneRules.items() %}
logI "Processing rules in zone {{z}}"
if ! firewall-cmd --permanent --list-all-zones | grep -ve '\(^[[:space:]]*$\|\ .*\)' | grep -q "{{z}}"; then
firewall-cmd --permanent --new-zone="{{z}}"
fi
{% if "openPorts" in rs %}
logI "Define opened ports"
{% for p in rs['openPorts'] %}
{% for s in p.sources %}
firewall-cmd --permanent --zone="{{z}}" --add-source="{{s}}"
{% endfor %}
firewall-cmd --permanent --zone={{z}} --add-port={{p.port}}/{{p.proto | lower }}
{% endfor %}
{% endif %}
{% endfor %}

View File

@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -euo pipefail
{% if DEBUG %}
set -x
{% endif %}
BASE_DIR="${TMP}/gpoA"
LOG_DIR="${BASE_DIR}/log"
#STDERR_TO="${LOG_DIR}/stderr.log"
__log () {
local level="$1"
local msg="$2"
local mod_name="${MODULE_NAME}"
echo "${mod_name} [${level}] ${msg}"
}
logD () {
__log "DEBUG" "$1"
}
logI () {
__log "INFO" "$1"
}
logW () {
__log "WARNING" "$1"
}
logE () {
__log "ERROR" "$1"
exit 1
}
__if_func_exists() {
declare -f -F "$1" >/dev/null
return $?
}
__on_exit() {
__if_func_exists "on_exit" && on_exit
logI "Finalizyng..."
}
__on_error() {
__if_func_exists "on_error" && on_exit
logE "Error..."
}
__init() {
[[ -d "${BASE_DIR}" ]] || mkdir "${BASE_DIR}"
[[ -d "${LOG_DIR}" ]] || mkdir "${LOG_DIR}"
# exec 2> ${STDERR_TO}
}
for sig in SIGINT SIGTERM SIGHUP SIGQUIT EXIT RETURN; do
trap __on_exit $sig
done
#for sig in ERR; do
# trap __on_error $sig
#done
__init

View File

@ -0,0 +1,16 @@
{% include "header.bash.j2" %}
MODULE_NAME="Printers"
{% for p in printers %}
{%set printer_name = p.name | replace(" ", "_") %}
{%set printer_address = p.addr %}
logI "Search if {{ printer_name }} is already defined"
URI=$(LC_ALL="C" lpstat -v | awk '$3 ~ /{{ printer_name }}:/ { print $4;}')
if [[ -z "$URI" ]]; then
logI "Register {{ printer_name }} on {{ printer_address }}"
lpadmin -p "{{ printer_name }}" -E -v "ipp://{{ printer_address }}/ipp/print" -m everywhere
logI "Done"
else
logI "{{ printer_name }} is already defined as ${URI}. Nothing to do."
fi
{% endfor %}

View File

@ -0,0 +1,25 @@
{% include "header.bash.j2" %}
MODULE_NAME="Removable Devices Perms"
mk_udisk2_rules() {
local allow="$1"
cat <<EOF >/etc/polkit-1/rules.d/60-udisks2.rules
polkit.addRule(function(action, subject) {
var YES = polkit.Result.YES;
var NO = polkit.Result.NO;
var perms = {
"org.freedesktop.udisks2.filesystem-mount": ${allow},
"org.freedesktop.udisks2.filesystem-mount-system": ${allow},
"org.freedesktop.udisks2.filesystem-mount-other-seat": ${allow},
};
// if (!subject.isInGroup("wheel")) {
return permission[action.id];
// }
});
EOF
}
{% if Removable is defined and (Removable.deny_exec or Removable.deny_read or Removable.deny_write) %}
mk_udisk2_rules "NO"
{% endif %}

View File

@ -0,0 +1,11 @@
{% include "header.bash.j2" %}
MODULE_NAME="Shortcuts"
DESKTOP_DIR=$(xdg-user-dir DESKTOP)
{% for s in shortcuts %}
cat <<EOF >"${DESKTOP_DIR}/{{s.name}}.desktop"
[Desktop Entry]
Name={{s.name}}
Exec={{s.target}} {{s.args}}
EOF
{% endfor %}

BIN
gpoa/test/Registry.pol Normal file

Binary file not shown.

BIN
gpoa/test/test.pol Normal file

Binary file not shown.

23
gpoa/test/test.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<PolFile num_entries="4" signature="PReg" version="1">
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>sshd-gssapi-auth</ValueName>
<Value>1</Value>
</Entry>
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>ssh-gssapi-auth</ValueName>
<Value>1</Value>
</Entry>
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>sudo</ValueName>
<Value>0</Value>
</Entry>
<Entry type="4" type_name="REG_DWORD">
<Key>Software\Policies\Microsoft\Windows\System</Key>
<ValueName>UserPolicyMode</ValueName>
<Value>1</Value>
</Entry>
</PolFile>

BIN
gpoa/test/test_reg.pol Normal file

Binary file not shown.

18
gpoa/test/test_reg.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<PolFile num_entries="3" signature="PReg" version="1">
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>sshd-gssapi-auth</ValueName>
<Value>1</Value>
</Entry>
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>ssh-gssapi-auth</ValueName>
<Value>1</Value>
</Entry>
<Entry type="4" type_name="REG_DWORD">
<Key>Software\BaseALT\Policies\Control</Key>
<ValueName>sudo</ValueName>
<Value>0</Value>
</Entry>
</PolFile>

306
gpupdate Executable file
View File

@ -0,0 +1,306 @@
#! /usr/bin/env python3
import argparse
import subprocess
import threading
import os
import errno
import tempfile
import shutil
import socket
import sys
import re
class gpupdate:
_smb_cache = '/var/cache/samba/gpo_cache'
_tmp_root = '/tmp/gpupdate'
def __init__(self, user, domain, controller):
self._gpos = []
self._user = user
self._domain = domain
self._controller = controller
try:
os.makedirs(self._tmp_root)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
self._temp_dir = self._mktmp('/tmp/gpupdate')
def update(self):
'''
Update GPO cache.
'''
proc = subprocess.Popen(['net', 'ads', 'gpo', 'list', self._user], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
#output = subprocess.check_output(['net', 'ads', 'gpo', 'list', self.user])
#print(output)
thr = threading.Thread(target=self._parse_netads, args=(proc,))
thr.start()
thr.join()
return self._fetch_gpos()
def _parse_netads(self, proc):
'''
Parse output of `net ads gpo list Administrator` command.
'''
for line in iter(proc.stdout.readline, b''):
decoded_line = line.decode('utf-8')
if decoded_line.startswith('filesyspath') and line != b'filesyspath:\t\t(null)\n':
self._gpos.append(self._strip_paths(decoded_line))
def _fetch_gpos(self):
'''
Download GPO directories specified by their GUIDs using smbclient with Kerberos 5 authentication.
'''
retrieved_gpos = []
for pol in self._gpos:
policy_tmp_dir = os.path.join(self._temp_dir, pol)
os.makedirs(policy_tmp_dir)
smbclient_cmd = 'prompt OFF;recurse ON;cd {}/Policies/{};lcd {};mget *'.format(self._domain.lower(), pol, policy_tmp_dir)
print('Executing: {}'.format(smbclient_cmd))
retcode = subprocess.call('smbclient -k \'\\\\{}\\sysvol\' -N -c \'{}\''.format(self._controller, smbclient_cmd), shell=True)
if retcode == 0:
print('Successfully retrieved GPO: {}'.format(pol))
self._2gpo_cache(policy_tmp_dir, pol)
retrieved_gpos.append(pol)
else:
print('Unable to retrieve GPO: {}'.format(pol))
return retrieved_gpos
def _mktmp(self, tmp):
'''
Create temporary directory to download GPO
'''
try:
os.makedirs(tmp)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
return tempfile.mkdtemp(dir=tmp)
def _strip_paths(self, policy_path):
'''
Strip newlines and extra symbols from paths.
'''
return policy_path[len('filesyspath:\t\t\\\\{}\\SysVol\\{}\\Policies\\'.format(self._domain, self._domain)):].strip()
def _2gpo_cache(self, obj, pol):
'''
Move downloaded GPOs to /var/cache/samba/gpo_cache
'''
gpo_dest = os.path.join(self._smb_cache, self._domain.upper(), 'POLICIES', pol)
shutil.rmtree(gpo_dest) # Remove destination GPO if exists
shutil.move(obj, gpo_dest)
class hreg:
_hreg_exe = '/usr/bin/hreg'
_hreg_cache = '/var/cache/gpupdate'
def __init__(self, cache_dir, domain, gpos, sid):
self._cache_dir = cache_dir
self._domain = domain
self._gpos = gpos
self._sid = sid
self._temp_dir = os.path.join(self._hreg_cache, 'tmp')
try:
os.makedirs(self._temp_dir)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
def to_fs(self):
'''
Transform policy files to file system representation for given domain
'''
dest = self._mktmp(self._temp_dir)
for gpo in self._gpos:
policy_file = os.path.join(self._cache_dir,
self._domain.upper(),
'POLICIES',
gpo,
'User',
'Registry.pol')
hreg_cmd = '{} apply {} {} -u {}'.format(self._hreg_exe, dest, policy_file, self._sid)
subprocess.call(hreg_cmd, shell=True)
# Move data to permanent cache
permanent_cache = os.path.join(self._hreg_cache, self._sid)
try:
shutil.rmtree(permanent_cache)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
pass
try:
os.makedirs(permanent_cache)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
for i in os.listdir(dest):
shutil.move(os.path.join(dest, i), permanent_cache)
shutil.rmtree(dest)
def _mktmp(self, tmp):
'''
Create temporary directory for GPO VFS
'''
try:
os.makedirs(tmp)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
return tempfile.mkdtemp(dir=tmp)
class gpoa:
_gpoa_dir = '/usr/libexec/gpoa'
_gpoa_exe = 'main.py'
def __init__(self, sid):
self._sid = sid
def generate_scripts(self):
'''
Call gpoa utility to generate scripts
'''
gpoa_cmd = ['./{}'.format(self._gpoa_exe), self._sid]
cwd = os.getcwd()
os.chdir(self._gpoa_dir)
print('Running gpoa')
output = subprocess.call(gpoa_cmd)
print(output)
os.chdir(cwd)
def wbinfo_getsid(domain, user):
'''
Get SID using wbinfo
'''
wbinfo_cmd = ['wbinfo', '-n', '{}\\{}'.format(domain.upper(), user)]
output = subprocess.check_output(wbinfo_cmd)
sid = output.split()[0].decode('utf-8')
return sid
def machine_kinit():
'''
Perform kinit with machine credentials
'''
host = socket.gethostname().split('.', 1)[0].upper() + "$"
subprocess.call(['kinit', '-k', host])
print('kinit succeed')
def check_krb_ticket():
'''
Check if Kerberos 5 ticket present
'''
try:
subprocess.check_call([ 'klist', '-s' ])
output = subprocess.check_output('klist', stderr=subprocess.STDOUT).decode()
print(output)
except:
sys.exit( 1 )
print('Ticket check succeed')
def get_domain_name():
'''
Get current Active Directory domain name
'''
lookup_cmd = ['net', 'ads', 'lookup']
output = subprocess.check_output(lookup_cmd, stderr=subprocess.STDOUT).decode()
d = re.search( "Domain:\s*(\S+)\n", output, re.MULTILINE )
if d:
domain_name = d.group(1)
print(domain_name)
return domain_name
return
def get_domain_controller():
'''
Get current Active Directory domain name
'''
lookup_cmd = ['net', 'ads', 'lookup']
output = subprocess.check_output(lookup_cmd, stderr=subprocess.STDOUT).decode()
d = re.search( "^Domain Controller:\s*(\S+)\n", output, re.MULTILINE )
if d:
domain_controller = d.group(1)
print(domain_controller)
return domain_controller
return
def parse_cli_arguments():
'''
Command line argument parser
'''
argparser = argparse.ArgumentParser(description='Update group policies for the specified user')
argparser.add_argument('-u',
'--user',
default='Administrator',
help='Name of the user for GPO update')
argparser.add_argument('-d',
'--domain',
help='Name of the AD domain for replication')
argparser.add_argument('-c',
'--controller',
help='AD controller to connect to')
argparser.add_argument('-p',
'--password',
help='Kerberos 5 password for the specified user')
argparser.add_argument('-i',
'--sid',
help='Specify SID for User target')
argparser.add_argument('-t',
'--target',
default='User',
help='Computer or User')
argparser.add_argument('-f',
'--force',
help='Reapply all policy settings')
argparser.add_argument('-w',
'--wait',
default=600,
help='Wait for specified number of seconds')
argparser.add_argument('-l',
'--logoff',
help='Force logoff after settings are applied')
argparser.add_argument('-b',
'--boot',
help='Force reboot after settings are applied')
argparser.add_argument('-s',
'--sync',
help='Perform next GPO application synchronously')
return argparser.parse_args()
def main():
args = parse_cli_arguments()
machine_kinit()
check_krb_ticket()
domain = get_domain_name()
print('Domain: {}'.format(domain))
controller = get_domain_controller()
print('Controller: {}'.format(controller))
updater = gpupdate(args.user, domain, controller)
retrieved_gpos = updater.update()
sid = wbinfo_getsid(domain, args.user)
hreg_util = hreg('/var/cache/samba/gpo_cache', domain, retrieved_gpos, sid)
hreg_util.to_fs()
gpo_applier = gpoa(sid)
gpo_applier.generate_scripts()
if __name__ == '__main__':
main()

40
gpupdate.spec Normal file
View File

@ -0,0 +1,40 @@
%define _unpackaged_files_terminate_build 1
Name: gpupdate
Version: 0.0.1
Release: alt1
Summary: GPT applier
License: MIT
Group: Other
Url: http://git.altlinux.org/
BuildArch: noarch
Requires: hreg
Requires: control
Requires: samba-dc
Source0: %name-%version.tar
%description
GPT applier
%prep
%setup -q
%install
install -pD -m755 gpupdate \
%buildroot%_bindir/gpupdate
mkdir -p \
%buildroot%prefix/libexec
cp -r gpoa \
%buildroot%prefix/libexec
%files
%prefix/bin/gpupdate
%prefix/libexec/gpoa
%changelog
* Thu Nov 14 2019 Igor Chudov <nir@altlinux.org> 0.0.1-alt1
- Initial release