def from_passwd(uid_min=None, uid_max=None): """Create collection from locally discovered data, e.g. /etc/passwd.""" import pwd users = Users(oktypes=User) passwd_list = pwd.getpwall() if not uid_min: uid_min = UID_MIN if not uid_max: uid_max = UID_MAX sudoers_entries = read_sudoers() for pwd_entry in passwd_list: if uid_min <= pwd_entry.pw_uid <= uid_max: user = User(name=text_type(pwd_entry.pw_name), passwd=text_type(pwd_entry.pw_passwd), uid=pwd_entry.pw_uid, gid=pwd_entry.pw_gid, gecos=text_type(pwd_entry.pw_gecos), home_dir=text_type(pwd_entry.pw_dir), shell=text_type(pwd_entry.pw_shell), public_keys=read_authorized_keys( username=pwd_entry.pw_name), sudoers_entry=get_sudoers_entry( username=pwd_entry.pw_name, sudoers_entries=sudoers_entries)) users.append(user) return users
def remove_sudoers_entry(username=None): """Remove sudoers entry. args: user (User): Instance of User containing sudoers entry. returns: str: sudoers entry for the specified user. """ sudoers_path = '/etc/sudoers' rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) tmp_sudoers_path = '/tmp/sudoers_{0}'.format(rnd_chars) execute_command( shlex.split(str('{0} cp {1} {2}'.format(sudo_check(), sudoers_path, tmp_sudoers_path)))) execute_command( shlex.split(str('{0} chmod 777 {1}'.format(sudo_check(), tmp_sudoers_path)))) with open(tmp_sudoers_path, mode=text_type('r')) as tmp_sudoers_file: sudoers_entries = tmp_sudoers_file.readlines() sudoers_output = list() for entry in sudoers_entries: if not entry.startswith(username): sudoers_output.append(entry) with open(tmp_sudoers_path, mode=text_type('w+')) as tmp_sudoers_file: tmp_sudoers_file.writelines(sudoers_output) execute_command( shlex.split(str('{0} cp {1} {2}'.format(sudo_check(), tmp_sudoers_path, sudoers_path)))) execute_command(shlex.split(str('{0} chown root:root {1}'.format(sudo_check(), sudoers_path)))) execute_command(shlex.split(str('{0} chmod 440 {1}'.format(sudo_check(), sudoers_path)))) execute_command(shlex.split(str('{0} rm {1}'.format(sudo_check(), tmp_sudoers_path))))
def raw(self): """Return raw key. returns: str: raw key """ if self._raw: return text_type(self._raw).strip("\r\n") else: return text_type(base64decode(self._b64encoded)).strip("\r\n")
def write_sudoers_entry(username=None, sudoers_entry=None): """Write sudoers entry. args: user (User): Instance of User containing sudoers entry. returns: str: sudoers entry for the specified user. """ sudoers_path = '/etc/sudoers' rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) tmp_sudoers_path = '/tmp/sudoers_{0}'.format(rnd_chars) execute_command( shlex.split( str('{0} cp {1} {2}'.format(sudo_check(), sudoers_path, tmp_sudoers_path)))) execute_command( shlex.split( str('{0} chmod 777 {1}'.format(sudo_check(), tmp_sudoers_path)))) with open(tmp_sudoers_path, mode=text_type('r')) as tmp_sudoers_file: sudoers_entries = tmp_sudoers_file.readlines() sudoers_output = list() for entry in sudoers_entries: if entry and not entry.startswith(username): sudoers_output.append(entry) if sudoers_entry: sudoers_output.append('{0} {1}'.format(username, sudoers_entry)) sudoers_output.append('\n') with open(tmp_sudoers_path, mode=text_type('w+')) as tmp_sudoers_file: tmp_sudoers_file.writelines(sudoers_output) sudoers_check_result = execute_command( shlex.split( str('{0} {1} -cf {2}'.format(sudo_check(), LINUX_CMD_VISUDO, tmp_sudoers_path)))) if sudoers_check_result[1] > 0: raise ValueError(sudoers_check_result[0][1]) execute_command( shlex.split( str('{0} cp {1} {2}'.format(sudo_check(), tmp_sudoers_path, sudoers_path)))) execute_command( shlex.split( str('{0} chown root:root {1}'.format(sudo_check(), sudoers_path)))) execute_command( shlex.split(str('{0} chmod 440 {1}'.format(sudo_check(), sudoers_path)))) execute_command( shlex.split(str('{0} rm {1}'.format(sudo_check(), tmp_sudoers_path))))
def gecos(self): """Force double quoted gecos. returns: str: The double quoted gecos. """ if not self._gecos: return None if self._gecos.startswith(text_type('\'')) and self._gecos.endswith(text_type('\'')): self._gecos = '\"{0}\"'.format(self._gecos[1:-1]) return self._gecos elif self._gecos.startswith(text_type('\"')) and self._gecos.endswith(text_type('\"')): return self._gecos else: return '\"{0}\"'.format(self._gecos)
def write_authorized_keys(user=None): """Write public keys back to authorized_keys file. Create keys directory if it doesn't already exist. args: user (User): Instance of User containing keys. returns: list: Authorised keys for the specified user. """ authorized_keys = list() authorized_keys_dir = '{0}/.ssh'.format(os.path.expanduser('~{0}'.format(user.name))) rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) authorized_keys_path = '{0}/authorized_keys'.format(authorized_keys_dir) tmp_authorized_keys_path = '/tmp/authorized_keys_{0}_{1}'.format(user.name, rnd_chars) if not os.path.isdir(authorized_keys_dir): execute_command(shlex.split(str('{0} mkdir -p {1}'.format(sudo_check(), authorized_keys_dir)))) for key in user.public_keys: authorized_keys.append('{0}\n'.format(key.raw)) with open(tmp_authorized_keys_path, mode=text_type('w+')) as keys_file: keys_file.writelines(authorized_keys) execute_command( shlex.split(str('{0} cp {1} {2}'.format(sudo_check(), tmp_authorized_keys_path, authorized_keys_path)))) execute_command(shlex.split(str('{0} chown -R {1} {2}'.format(sudo_check(), user.name, authorized_keys_dir)))) execute_command(shlex.split(str('{0} chmod 700 {1}'.format(sudo_check(), authorized_keys_dir)))) execute_command(shlex.split(str('{0} chmod 600 {1}'.format(sudo_check(), authorized_keys_path)))) execute_command(shlex.split(str('{0} rm {1}'.format(sudo_check(), tmp_authorized_keys_path))))
def gecos(self): """Force double quoted gecos. returns: str: The double quoted gecos. """ if not self._gecos: return None if self._gecos.startswith(text_type('\'')) and self._gecos.endswith( text_type('\'')): self._gecos = '\"{0}\"'.format(self._gecos[1:-1]) return self._gecos elif self._gecos.startswith(text_type('\"')) and self._gecos.endswith( text_type('\"')): return self._gecos else: return '\"{0}\"'.format(self._gecos)
def from_json(cls, file_path=None): """Create collection from a JSON file.""" with io.open(file_path, encoding=text_type('utf-8')) as stream: try: users_json = json.load(stream) except ValueError: raise ValueError('No JSON object could be decoded') return cls.construct_user_list(raw_users=users_json.get('users'))
def export(self, file_path=None, export_format=None): """ Write the users to a file. """ with io.open(file_path, mode='w', encoding="utf-8") as export_file: if export_format == 'yaml': import yaml yaml.safe_dump(self.to_dict(), export_file, default_flow_style=False) elif export_format == 'json': export_file.write(text_type(json.dumps(self.to_dict(), ensure_ascii=False))) return True
def b64encoded(self): """Return a base64 encoding of the key. returns: str: base64 encoding of the public key """ if self._b64encoded: return text_type(self._b64encoded).strip("\r\n") else: return base64encode(self.raw)
def export(self, file_path=None, export_format=None): """ Write the users to a file. """ with io.open(file_path, mode='w', encoding="utf-8") as export_file: if export_format == 'yaml': import yaml yaml.safe_dump(self.to_dict(), export_file, default_flow_style=False) elif export_format == 'json': export_file.write( text_type(json.dumps(self.to_dict(), ensure_ascii=False))) return True
def remove_sudoers_entry(username=None): """Remove sudoers entry. args: user (User): Instance of User containing sudoers entry. returns: str: sudoers entry for the specified user. """ sudoers_path = '/etc/sudoers' rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) tmp_sudoers_path = '/tmp/sudoers_{0}'.format(rnd_chars) execute_command( shlex.split( str('{0} cp {1} {2}'.format(sudo_check(), sudoers_path, tmp_sudoers_path)))) execute_command( shlex.split( str('{0} chmod 777 {1}'.format(sudo_check(), tmp_sudoers_path)))) with open(tmp_sudoers_path, mode=text_type('r')) as tmp_sudoers_file: sudoers_entries = tmp_sudoers_file.readlines() sudoers_output = list() for entry in sudoers_entries: if not entry.startswith(username): sudoers_output.append(entry) with open(tmp_sudoers_path, mode=text_type('w+')) as tmp_sudoers_file: tmp_sudoers_file.writelines(sudoers_output) execute_command( shlex.split( str('{0} cp {1} {2}'.format(sudo_check(), tmp_sudoers_path, sudoers_path)))) execute_command( shlex.split( str('{0} chown root:root {1}'.format(sudo_check(), sudoers_path)))) execute_command( shlex.split(str('{0} chmod 440 {1}'.format(sudo_check(), sudoers_path)))) execute_command( shlex.split(str('{0} rm {1}'.format(sudo_check(), tmp_sudoers_path))))
def login_defs(): """Discover the minimum and maximum UID number.""" uid_min = None uid_max = None login_defs_path = '/etc/login.defs' if os.path.exists(login_defs_path): with io.open(text_type(login_defs_path), encoding=text_type('utf-8')) as log_defs_file: login_data = log_defs_file.readlines() for line in login_data: if PY3: # pragma: no cover line = str(line) if PY2: # pragma: no cover line = line.encode(text_type('utf8')) if line[:7] == text_type('UID_MIN'): uid_min = int(line.split()[1].strip()) if line[:7] == text_type('UID_MAX'): uid_max = int(line.split()[1].strip()) if not uid_min: # pragma: no cover uid_min = DEFAULT_UID_MIN if not uid_max: # pragma: no cover uid_max = DEFAULT_UID_MAX return uid_min, uid_max
def write_sudoers_entry(username=None, sudoers_entry=None): """Write sudoers entry. args: user (User): Instance of User containing sudoers entry. returns: str: sudoers entry for the specified user. """ sudoers_path = '/etc/sudoers' rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) tmp_sudoers_path = '/tmp/sudoers_{0}'.format(rnd_chars) execute_command( shlex.split(str('{0} cp {1} {2}'.format(sudo_check(), sudoers_path, tmp_sudoers_path)))) execute_command( shlex.split(str('{0} chmod 777 {1}'.format(sudo_check(), tmp_sudoers_path)))) with open(tmp_sudoers_path, mode=text_type('r')) as tmp_sudoers_file: sudoers_entries = tmp_sudoers_file.readlines() sudoers_output = list() for entry in sudoers_entries: if entry and not entry.startswith(username): sudoers_output.append(entry) if sudoers_entry: sudoers_output.append('{0} {1}'.format(username, sudoers_entry)) sudoers_output.append('\n') with open(tmp_sudoers_path, mode=text_type('w+')) as tmp_sudoers_file: tmp_sudoers_file.writelines(sudoers_output) sudoers_check_result = execute_command( shlex.split(str('{0} {1} -cf {2}'.format(sudo_check(), LINUX_CMD_VISUDO, tmp_sudoers_path)))) if sudoers_check_result[1] > 0: raise ValueError(sudoers_check_result[0][1]) execute_command( shlex.split(str('{0} cp {1} {2}'.format(sudo_check(), tmp_sudoers_path, sudoers_path)))) execute_command(shlex.split(str('{0} chown root:root {1}'.format(sudo_check(), sudoers_path)))) execute_command(shlex.split(str('{0} chmod 440 {1}'.format(sudo_check(), sudoers_path)))) execute_command(shlex.split(str('{0} rm {1}'.format(sudo_check(), tmp_sudoers_path))))
def from_passwd(uid_min=None, uid_max=None): """Create collection from locally discovered data, e.g. /etc/passwd.""" import pwd users = Users(oktypes=User) passwd_list = pwd.getpwall() if not uid_min: uid_min = UID_MIN if not uid_max: uid_max = UID_MAX sudoers_entries = read_sudoers() for pwd_entry in passwd_list: if uid_min <= pwd_entry.pw_uid <= uid_max: user = User(name=text_type(pwd_entry.pw_name), passwd=text_type(pwd_entry.pw_passwd), uid=pwd_entry.pw_uid, gid=pwd_entry.pw_gid, gecos=text_type(pwd_entry.pw_gecos), home_dir=text_type(pwd_entry.pw_dir), shell=text_type(pwd_entry.pw_shell), public_keys=read_authorized_keys(username=pwd_entry.pw_name), sudoers_entry=get_sudoers_entry(username=pwd_entry.pw_name, sudoers_entries=sudoers_entries)) users.append(user) return users
def test_create_and_execute_plan_to_create_new_user_with_sudo_all(): """ Test creation of a user instance with sudo all and then write """ delete_test_user_and_group() create_test_group() current_users = Users.from_passwd() provided_users = Users() public_keys = [PublicKey(b64encoded=PUBLIC_KEYS[0]['encoded'])] provided_users.append( User(name='testuserx1234', home_dir='/home/testuserx1234', shell='/bin/false', gid=59999, uid=59999, gecos='test user gecos', public_keys=public_keys, sudoers_entry='ALL=(ALL)\tNOPASSWD:ALL')) plan = create_plan(existing_users=current_users, proposed_users=provided_users, purge_undefined=True, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'vagrant', CURRENT_USER ]) assert plan[0]['state'] == 'missing' assert plan[0]['proposed_user'].name == "testuserx1234" assert plan[0]['proposed_user'].home_dir == "/home/testuserx1234" assert plan[0]['proposed_user'].uid == 59999 assert plan[0]['proposed_user'].gid == 59999 assert plan[0]['proposed_user'].gecos == '\"test user gecos\"' assert plan[0]['proposed_user'].shell == '/bin/false' assert plan[0]['proposed_user'].sudoers_entry == 'ALL=(ALL)\tNOPASSWD:ALL' assert type(plan[0]['proposed_user'].public_keys[0].raw) == text_type assert plan[0]['proposed_user'].public_keys[0].raw == text_type( PUBLIC_KEYS[0]['raw']) execute_plan(plan=plan) current_users = Users.from_passwd() created_user = current_users.describe_users(users_filter=dict( name='testuserx1234')) assert created_user[0].sudoers_entry == 'ALL=(ALL)\tNOPASSWD:ALL' plan = create_plan(existing_users=current_users, proposed_users=provided_users, purge_undefined=True, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'nginx', 'vagrant', CURRENT_USER ]) assert not plan
def write_authorized_keys(user=None): """Write public keys back to authorized_keys file. Create keys directory if it doesn't already exist. args: user (User): Instance of User containing keys. returns: list: Authorised keys for the specified user. """ authorized_keys = list() authorized_keys_dir = '{0}/.ssh'.format( os.path.expanduser('~{0}'.format(user.name))) rnd_chars = random_string(length=RANDOM_FILE_EXT_LENGTH) authorized_keys_path = '{0}/authorized_keys'.format(authorized_keys_dir) tmp_authorized_keys_path = '/tmp/authorized_keys_{0}_{1}'.format( user.name, rnd_chars) if not os.path.isdir(authorized_keys_dir): execute_command( shlex.split( str('{0} mkdir -p {1}'.format(sudo_check(), authorized_keys_dir)))) for key in user.public_keys: authorized_keys.append('{0}\n'.format(key.raw)) with open(tmp_authorized_keys_path, mode=text_type('w+')) as keys_file: keys_file.writelines(authorized_keys) execute_command( shlex.split( str('{0} cp {1} {2}'.format(sudo_check(), tmp_authorized_keys_path, authorized_keys_path)))) execute_command( shlex.split( str('{0} chown -R {1} {2}'.format(sudo_check(), user.name, authorized_keys_dir)))) execute_command( shlex.split( str('{0} chmod 700 {1}'.format(sudo_check(), authorized_keys_dir)))) execute_command( shlex.split( str('{0} chmod 600 {1}'.format(sudo_check(), authorized_keys_path)))) execute_command( shlex.split( str('{0} rm {1}'.format(sudo_check(), tmp_authorized_keys_path))))
def from_yaml(cls, file_path=None): """Create collection from a YAML file.""" try: import yaml except ImportError: # pragma: no cover yaml = None if not yaml: import sys sys.exit('PyYAML is not installed, but is required in order to parse YAML files.' '\nTo install, run:\n$ pip install PyYAML\nor visit' ' http://pyyaml.org/wiki/PyYAML for instructions.') with io.open(file_path, encoding=text_type('utf-8')) as stream: users_yaml = yaml.safe_load(stream) if isinstance(users_yaml, dict): return cls.construct_user_list(raw_users=users_yaml.get('users')) else: raise ValueError('No YAML object could be decoded')
def from_yaml(cls, file_path=None): """Create collection from a YAML file.""" try: import yaml except ImportError: # pragma: no cover yaml = None if not yaml: import sys sys.exit( 'PyYAML is not installed, but is required in order to parse YAML files.' '\nTo install, run:\n$ pip install PyYAML\nor visit' ' http://pyyaml.org/wiki/PyYAML for instructions.') with io.open(file_path, encoding=text_type('utf-8')) as stream: users_yaml = yaml.safe_load(stream) if isinstance(users_yaml, dict): return cls.construct_user_list( raw_users=users_yaml.get('users')) else: raise ValueError('No YAML object could be decoded')
def test_execute_plan_to_update_existing_user(): """ Create a new user and then attempt to create another user with existing id """ delete_test_user_and_group() create_test_user() raw_public_key_2 = PUBLIC_KEYS[1].get('raw') public_key_2 = PublicKey(raw=raw_public_key_2) current_users = Users.from_passwd() provided_users = Users() provided_users.append( User(name='testuserx1234', uid=59998, gid=1, gecos='test user gecos update', shell='/bin/false', public_keys=[public_key_2], sudoers_entry='ALL=(ALL:ALL) ALL')) plan = create_plan(existing_users=current_users, proposed_users=provided_users, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'nginx', 'hadfielj', 'vagrant', CURRENT_USER ]) assert plan[0]['proposed_user'].gecos == '\"test user gecos update\"' execute_plan(plan=plan) updated_users = Users.from_passwd() updated_user = updated_users.describe_users(users_filter=dict( name='testuserx1234')) assert len(updated_user) == 1 assert updated_user[0].name == 'testuserx1234' assert updated_user[0].uid == 59998 assert updated_user[0].gid == 1 assert updated_user[0].gecos == '\"test user gecos update\"' assert updated_user[0].shell == '/bin/false' assert updated_user[0].public_keys[0].raw == text_type( PUBLIC_KEYS[1]['raw']) assert updated_user[0].sudoers_entry == 'ALL=(ALL:ALL) ALL' delete_test_user_and_group()
def test_execute_plan_to_update_existing_user_with_multiple_keys(): """ Create a new user with 2 keys and then replace with a new one """ create_test_user() raw_public_key_1 = PUBLIC_KEYS[0].get('raw') public_key_1 = PublicKey(raw=raw_public_key_1) raw_public_key_2 = PUBLIC_KEYS[1].get('raw') public_key_2 = PublicKey(raw=raw_public_key_2) raw_public_key_3 = PUBLIC_KEYS[2].get('raw') public_key_3 = PublicKey(raw=raw_public_key_3) raw_public_key_4 = PUBLIC_KEYS[3].get('raw') public_key_4 = PublicKey(raw=raw_public_key_4) current_users = Users.from_passwd() provided_users_2 = Users() provided_users_2.append( User(name='testuserx1234', uid=59998, gid=1, gecos='test user gecos update', shell='/bin/false', public_keys=[public_key_1, public_key_2])) plan = create_plan(existing_users=current_users, proposed_users=provided_users_2, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'nginx', 'hadfielj', 'vagrant', CURRENT_USER ]) execute_plan(plan=plan) updated_users = Users.from_passwd() updated_user = updated_users.describe_users(users_filter=dict( name='testuserx1234')) assert updated_user[0].public_keys[0].raw == text_type( PUBLIC_KEYS[0]['raw']) assert updated_user[0].public_keys[1].raw == text_type( PUBLIC_KEYS[1]['raw']) # Replace both keys current_users = Users.from_passwd() provided_users_3 = Users() provided_users_3.append( User(name='testuserx1234', uid=59998, gid=1, gecos='test user gecos update', shell='/bin/false', public_keys=[public_key_3, public_key_4])) plan = create_plan(existing_users=current_users, proposed_users=provided_users_3, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'nginx', 'hadfielj', 'vagrant', CURRENT_USER ]) execute_plan(plan=plan) updated_users = Users.from_passwd() updated_user = updated_users.describe_users(users_filter=dict( name='testuserx1234')) assert updated_user[0].public_keys[0].raw == text_type( PUBLIC_KEYS[2]['raw']) assert updated_user[0].public_keys[1].raw == text_type( PUBLIC_KEYS[3]['raw']) # Replace one key current_users = Users.from_passwd() provided_users_4 = Users() provided_users_4.append( User(name='testuserx1234', uid=59998, gid=1, gecos='test user gecos update', shell='/bin/false', public_keys=[public_key_2, public_key_4])) plan = create_plan(existing_users=current_users, proposed_users=provided_users_4, protected_users=[ 'travis', 'couchdb', 'ubuntu', 'nginx', 'hadfielj', 'vagrant', CURRENT_USER ]) execute_plan(plan=plan) updated_users = Users.from_passwd() updated_user = updated_users.describe_users(users_filter=dict( name='testuserx1234')) assert updated_user[0].public_keys[0].raw == text_type( PUBLIC_KEYS[1]['raw']) assert updated_user[0].public_keys[1].raw == text_type( PUBLIC_KEYS[3]['raw']) delete_test_user_and_group()