New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Synchronize root and admin password #527
Changes from 3 commits
7a3ba81
6aef80c
2d5077e
e150c41
7975d73
2bb75d1
5e91b5c
ae3bf28
feb4557
848a6b6
3f3069d
a94e69f
fb2d85b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import spwd | ||
import crypt | ||
import random | ||
import string | ||
import subprocess | ||
|
||
from moulinette import m18n | ||
from moulinette.core import MoulinetteError | ||
from moulinette.utils.log import getActionLogger | ||
from moulinette.utils.process import run_commands | ||
from moulinette.utils.filesystem import append_to_file | ||
from moulinette.authenticators.ldap import Authenticator | ||
|
||
from yunohost.tools import Migration | ||
|
||
logger = getActionLogger('yunohost.migration') | ||
SMALL_PWD_LIST = ["yunohost", "olinux"] | ||
|
||
class MyMigration(Migration): | ||
"Migrate password" | ||
|
||
def migrate(self): | ||
|
||
if self._is_root_pwd_listed(SMALL_PWD_LIST): | ||
new_hash = self._get_admin_hash() | ||
self._replace_root_hash(new_hash) | ||
|
||
def backward(self): | ||
|
||
pass | ||
|
||
def _get_admin_hash(self): | ||
""" | ||
Ask for admin hash the ldap db | ||
Note: to do that like we don't know the admin password we add a second | ||
password | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm testing this and : is this method really needed to get the admin hash ? I just realized that, as root, you can run
|
||
""" | ||
logger.debug('Generate a random temporary password') | ||
tmp_password = ''.join(random.choice(string.ascii_letters + | ||
string.digits) for i in range(12)) | ||
|
||
# Generate a random temporary password (won't be valid after this | ||
# script ends !) and hash it | ||
logger.debug('Hash temporary password') | ||
tmp_hash = subprocess.check_output(["slappasswd", "-h", "{SSHA}","-s", | ||
tmp_password]) | ||
|
||
try: | ||
logger.debug('Stop slapd and backup its conf') | ||
run_commands([ | ||
# Stop slapd service... | ||
'systemctl stop slapd', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm that one looks kinda touchy 😛 I wonder if that's really a good idea (though there are not so many alternatives). I'm guessing that the fact that we're logged as admin (e.g. from the webadmin) is cached somewhere (unscd?) so that should be okay ... But we should reaaaally make sure that slapd gets back on at some point.... c.f. the "slapd start" at the end of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. run_commands raise a CalledProcessError at the first command that fail. We can disable this behavious by passing a specific callback that don't raise error. |
||
|
||
# Backup slapd.conf (to be restored at the end of script) | ||
'cp /etc/ldap/slapd.conf /root/slapd.conf.bkp' | ||
]) | ||
|
||
logger.debug('Add password to the conf') | ||
# Append lines to slapd.conf to manually define root password hash | ||
append_to_file("/etc/ldap/slapd.conf", 'rootdn "cn=admin,dc=yunohost,dc=org"') | ||
append_to_file("/etc/ldap/slapd.conf", "\n") | ||
append_to_file("/etc/ldap/slapd.conf", 'rootpw ' + tmp_hash) | ||
|
||
logger.debug('Start slapd with new password') | ||
run_commands([ | ||
# Test conf (might not be entirely necessary though :P) | ||
'slaptest -Q -u -f /etc/ldap/slapd.conf', | ||
|
||
# Regenerate slapd.d directory | ||
'rm -Rf /etc/ldap/slapd.d', | ||
'mkdir /etc/ldap/slapd.d', | ||
'slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1', | ||
|
||
# Set permissions to slapd.d | ||
'chown -R openldap:openldap /etc/ldap/slapd.d/', | ||
|
||
# Restore slapd.conf | ||
'mv /root/slapd.conf.bkp /etc/ldap/slapd.conf', | ||
|
||
# Restart slapd service | ||
'service slapd start' | ||
]) | ||
|
||
logger.debug('Authenticate on ldap') | ||
auth = Authenticator('default', 'ldap://localhost:389', | ||
'dc=yunohost,dc=org', 'cn=admin') | ||
auth.authenticate( tmp_password) | ||
logger.debug('Ask for the admin hash') | ||
admin_hash = auth.search('cn=admin,dc=yunohost,dc=org', 'cn=admin', | ||
['userPassword'])[0]['userPassword'][0] | ||
admin_hash = admin_hash.replace('{CRYPT}', '') | ||
finally: | ||
logger.debug('Remove tmp_password from ldap db') | ||
# Remove tmp_password from ldap db | ||
run_commands([ | ||
|
||
# Stop slapd service | ||
'service slapd stop || true', | ||
|
||
'if [ -f /root/slapd.conf.bkp ]; then mv /root/slapd.conf.bkp /etc/ldap/slapd.conf; fi', | ||
|
||
# Regenerate slapd.d directory | ||
'rm -Rf /etc/ldap/slapd.d', | ||
'mkdir /etc/ldap/slapd.d', | ||
'slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1', | ||
|
||
# Set permissions to slapd.d | ||
'chown -R openldap:openldap /etc/ldap/slapd.d/', | ||
|
||
# Restart slapd service | ||
'service slapd start' | ||
]) | ||
return admin_hash | ||
|
||
|
||
def _replace_root_hash(self, new_hash): | ||
hash_root = spwd.getspnam("root").sp_pwd | ||
|
||
with open('/etc/shadow', 'r') as before_file: | ||
before = before_file.read() | ||
|
||
with open('/etc/shadow', 'w') as after_file: | ||
after_file.write(before.replace("root:" + hash_root, | ||
"root:" + new_hash)) | ||
|
||
def _is_root_pwd_listed(self, pwd_list): | ||
hash_root = spwd.getspnam("root").sp_pwd | ||
|
||
for password in pwd_list: | ||
if hash_root == crypt.crypt(password, hash_root): | ||
return True | ||
return False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rethinking about this, I think we need to clarify the purpose of this migration ? (Or maybe I'm kinda lost about it)
It seems like this PR does two different things :
But for existing instance on which the root password ain't "bad", after this migration, we still end up with the root password and admin password being desynchronized. Shouldn't we sync the root and admin password in any case so that we end up with consistent setups in all cases ?