mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Drop the 'admin' user, have 'admins' be a group of Yunohost users instead
This commit is contained in:
parent
beadea5305
commit
6cae524910
13 changed files with 125 additions and 166 deletions
|
@ -130,7 +130,6 @@ olcSuffix: dc=yunohost,dc=org
|
||||||
# admin entry below
|
# admin entry below
|
||||||
# These access lines apply to database #1 only
|
# These access lines apply to database #1 only
|
||||||
olcAccess: {0}to attrs=userPassword,shadowLastChange
|
olcAccess: {0}to attrs=userPassword,shadowLastChange
|
||||||
by dn.base="cn=admin,dc=yunohost,dc=org" write
|
|
||||||
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
||||||
by anonymous auth
|
by anonymous auth
|
||||||
by self write
|
by self write
|
||||||
|
@ -140,7 +139,6 @@ olcAccess: {0}to attrs=userPassword,shadowLastChange
|
||||||
# owning it if they are authenticated.
|
# owning it if they are authenticated.
|
||||||
# Others should be able to see it.
|
# Others should be able to see it.
|
||||||
olcAccess: {1}to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn
|
olcAccess: {1}to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn
|
||||||
by dn.base="cn=admin,dc=yunohost,dc=org" write
|
|
||||||
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
||||||
by self write
|
by self write
|
||||||
by * read
|
by * read
|
||||||
|
@ -160,9 +158,7 @@ olcAccess: {2}to dn.base=""
|
||||||
# The admin dn has full write access, everyone else
|
# The admin dn has full write access, everyone else
|
||||||
# can read everything.
|
# can read everything.
|
||||||
olcAccess: {3}to *
|
olcAccess: {3}to *
|
||||||
by dn.base="cn=admin,dc=yunohost,dc=org" write
|
|
||||||
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
|
||||||
by group/groupOfNames/member.exact="cn=admin,ou=groups,dc=yunohost,dc=org" write
|
|
||||||
by * read
|
by * read
|
||||||
#
|
#
|
||||||
olcAddContentAcl: FALSE
|
olcAddContentAcl: FALSE
|
||||||
|
|
|
@ -5,15 +5,6 @@ objectClass: organization
|
||||||
o: yunohost.org
|
o: yunohost.org
|
||||||
dc: yunohost
|
dc: yunohost
|
||||||
|
|
||||||
dn: cn=admin,ou=sudo,dc=yunohost,dc=org
|
|
||||||
cn: admin
|
|
||||||
objectClass: sudoRole
|
|
||||||
objectClass: top
|
|
||||||
sudoCommand: ALL
|
|
||||||
sudoUser: admin
|
|
||||||
sudoOption: !authenticate
|
|
||||||
sudoHost: ALL
|
|
||||||
|
|
||||||
dn: ou=users,dc=yunohost,dc=org
|
dn: ou=users,dc=yunohost,dc=org
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
||||||
objectClass: top
|
objectClass: top
|
||||||
|
@ -39,28 +30,30 @@ objectClass: organizationalUnit
|
||||||
objectClass: top
|
objectClass: top
|
||||||
ou: groups
|
ou: groups
|
||||||
|
|
||||||
|
dn: cn=admins,ou=sudo,dc=yunohost,dc=org
|
||||||
|
cn: admins
|
||||||
|
objectClass: sudoRole
|
||||||
|
objectClass: top
|
||||||
|
sudoCommand: ALL
|
||||||
|
sudoUser: %admins
|
||||||
|
sudoHost: ALL
|
||||||
|
|
||||||
dn: ou=sudo,dc=yunohost,dc=org
|
dn: ou=sudo,dc=yunohost,dc=org
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
||||||
objectClass: top
|
objectClass: top
|
||||||
ou: sudo
|
ou: sudo
|
||||||
|
|
||||||
dn: cn=admin,dc=yunohost,dc=org
|
|
||||||
objectClass: organizationalRole
|
|
||||||
objectClass: posixAccount
|
|
||||||
objectClass: simpleSecurityObject
|
|
||||||
cn: admin
|
|
||||||
uid: admin
|
|
||||||
uidNumber: 1007
|
|
||||||
gidNumber: 1007
|
|
||||||
homeDirectory: /home/admin
|
|
||||||
loginShell: /bin/bash
|
|
||||||
userPassword: yunohost
|
|
||||||
|
|
||||||
dn: cn=admins,ou=groups,dc=yunohost,dc=org
|
dn: cn=admins,ou=groups,dc=yunohost,dc=org
|
||||||
objectClass: posixGroup
|
objectClass: posixGroup
|
||||||
objectClass: top
|
objectClass: top
|
||||||
memberUid: admin
|
objectClass: groupOfNamesYnh
|
||||||
|
objectClass: mailGroup
|
||||||
gidNumber: 4001
|
gidNumber: 4001
|
||||||
|
mail: root
|
||||||
|
mail: admin
|
||||||
|
mail: webmaster
|
||||||
|
mail: postmaster
|
||||||
|
mail: abuse
|
||||||
cn: admins
|
cn: admins
|
||||||
|
|
||||||
dn: cn=all_users,ou=groups,dc=yunohost,dc=org
|
dn: cn=all_users,ou=groups,dc=yunohost,dc=org
|
||||||
|
|
|
@ -89,4 +89,7 @@ olcObjectClasses: ( 1.3.6.1.4.1.40328.1.1.2.3
|
||||||
NAME 'mailGroup' SUP top AUXILIARY
|
NAME 'mailGroup' SUP top AUXILIARY
|
||||||
DESC 'Mail Group'
|
DESC 'Mail Group'
|
||||||
MUST ( mail )
|
MUST ( mail )
|
||||||
|
MAY (
|
||||||
|
mailalias $ maildrop
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -42,7 +42,7 @@ do_init_regen() {
|
||||||
# Backup folders
|
# Backup folders
|
||||||
mkdir -p /home/yunohost.backup/archives
|
mkdir -p /home/yunohost.backup/archives
|
||||||
chmod 750 /home/yunohost.backup/archives
|
chmod 750 /home/yunohost.backup/archives
|
||||||
chown root:root /home/yunohost.backup/archives # This is later changed to admin:root once admin user exists
|
chown root:root /home/yunohost.backup/archives # This is later changed to root:admins once the admins group exists
|
||||||
|
|
||||||
# Empty ssowat json persistent conf
|
# Empty ssowat json persistent conf
|
||||||
echo "{}" >'/etc/ssowat/conf.json.persistent'
|
echo "{}" >'/etc/ssowat/conf.json.persistent'
|
||||||
|
@ -173,12 +173,11 @@ do_post_regen() {
|
||||||
# Enfore permissions #
|
# Enfore permissions #
|
||||||
######################
|
######################
|
||||||
|
|
||||||
chmod 750 /home/admin
|
chmod 770 /home/yunohost.backup
|
||||||
chmod 750 /home/yunohost.backup
|
chmod 770 /home/yunohost.backup/archives
|
||||||
chmod 750 /home/yunohost.backup/archives
|
|
||||||
chmod 700 /var/cache/yunohost
|
chmod 700 /var/cache/yunohost
|
||||||
chown admin:root /home/yunohost.backup
|
chown root:admins /home/yunohost.backup
|
||||||
chown admin:root /home/yunohost.backup/archives
|
chown root:admins /home/yunohost.backup/archives
|
||||||
chown root:root /var/cache/yunohost
|
chown root:root /var/cache/yunohost
|
||||||
|
|
||||||
# NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs
|
# NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs
|
||||||
|
|
|
@ -58,14 +58,6 @@ EOF
|
||||||
nscd -i passwd || true
|
nscd -i passwd || true
|
||||||
|
|
||||||
systemctl restart slapd
|
systemctl restart slapd
|
||||||
|
|
||||||
# We don't use mkhomedir_helper because 'admin' may not be recognized
|
|
||||||
# when this script is ran in a chroot (e.g. ISO install)
|
|
||||||
# We also refer to admin as uid 1007 for the same reason
|
|
||||||
if [ ! -d /home/admin ]; then
|
|
||||||
cp -r /etc/skel /home/admin
|
|
||||||
chown -R 1007:1007 /home/admin
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_regenerate_slapd_conf() {
|
_regenerate_slapd_conf() {
|
||||||
|
@ -172,22 +164,6 @@ objectClass: top"
|
||||||
|
|
||||||
echo "Reloading slapd"
|
echo "Reloading slapd"
|
||||||
systemctl force-reload slapd
|
systemctl force-reload slapd
|
||||||
|
|
||||||
# on slow hardware/vm this regen conf would exit before the admin user that
|
|
||||||
# is stored in ldap is available because ldap seems to slow to restart
|
|
||||||
# so we'll wait either until we are able to log as admin or until a timeout
|
|
||||||
# is reached
|
|
||||||
# we need to do this because the next hooks executed after this one during
|
|
||||||
# postinstall requires to run as admin thus breaking postinstall on slow
|
|
||||||
# hardware which mean yunohost can't be correctly installed on those hardware
|
|
||||||
# and this sucks
|
|
||||||
# wait a maximum time of 5 minutes
|
|
||||||
# yes, force-reload behave like a restart
|
|
||||||
number_of_wait=0
|
|
||||||
while ! su admin -c '' && ((number_of_wait < 60)); do
|
|
||||||
sleep 5
|
|
||||||
((number_of_wait += 1))
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do_$1_regen ${@:2}
|
do_$1_regen ${@:2}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
"admin_password": "Administration password",
|
"admin_password": "Administration password",
|
||||||
"admin_password_change_failed": "Unable to change password",
|
"admin_password_change_failed": "Unable to change password",
|
||||||
"admin_password_changed": "The administration password was changed",
|
"admin_password_changed": "The administration password was changed",
|
||||||
"admin_password_too_long": "Please choose a password shorter than 127 characters",
|
|
||||||
"already_up_to_date": "Nothing to do. Everything is already up-to-date.",
|
"already_up_to_date": "Nothing to do. Everything is already up-to-date.",
|
||||||
"app_action_broke_system": "This action seems to have broken these important services: {services}",
|
"app_action_broke_system": "This action seems to have broken these important services: {services}",
|
||||||
"app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).",
|
"app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).",
|
||||||
|
@ -534,6 +533,7 @@
|
||||||
"password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters",
|
"password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters",
|
||||||
"password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters",
|
"password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters",
|
||||||
"password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters",
|
"password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters",
|
||||||
|
"password_too_long": "Please choose a password shorter than 127 characters",
|
||||||
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only",
|
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only",
|
||||||
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
||||||
"pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)",
|
"pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)",
|
||||||
|
@ -685,5 +685,5 @@
|
||||||
"yunohost_configured": "YunoHost is now configured",
|
"yunohost_configured": "YunoHost is now configured",
|
||||||
"yunohost_installing": "Installing YunoHost...",
|
"yunohost_installing": "Installing YunoHost...",
|
||||||
"yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'",
|
"yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'",
|
||||||
"yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create <username>' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc."
|
"yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1438,10 +1438,10 @@ tools:
|
||||||
category_help: Specific tools
|
category_help: Specific tools
|
||||||
actions:
|
actions:
|
||||||
|
|
||||||
### tools_adminpw()
|
### tools_rootpw()
|
||||||
adminpw:
|
rootpw:
|
||||||
action_help: Change password of admin and root users
|
action_help: Change root password
|
||||||
api: PUT /adminpw
|
api: PUT /rootpw
|
||||||
arguments:
|
arguments:
|
||||||
-n:
|
-n:
|
||||||
full: --new-password
|
full: --new-password
|
||||||
|
@ -1476,6 +1476,25 @@ tools:
|
||||||
ask: ask_main_domain
|
ask: ask_main_domain
|
||||||
pattern: *pattern_domain
|
pattern: *pattern_domain
|
||||||
required: True
|
required: True
|
||||||
|
-u:
|
||||||
|
full: --username
|
||||||
|
help: Username for the first (admin) user
|
||||||
|
extra:
|
||||||
|
ask: ask_username
|
||||||
|
pattern: *pattern_username
|
||||||
|
required: True
|
||||||
|
-f:
|
||||||
|
full: --firstname
|
||||||
|
extra:
|
||||||
|
ask: ask_firstname
|
||||||
|
required: True
|
||||||
|
pattern: *pattern_firstname
|
||||||
|
-l:
|
||||||
|
full: --lastname
|
||||||
|
extra:
|
||||||
|
ask: ask_lastname
|
||||||
|
required: True
|
||||||
|
pattern: *pattern_lastname
|
||||||
-p:
|
-p:
|
||||||
full: --password
|
full: --password
|
||||||
help: YunoHost admin password
|
help: YunoHost admin password
|
||||||
|
@ -1487,14 +1506,10 @@ tools:
|
||||||
--ignore-dyndns:
|
--ignore-dyndns:
|
||||||
help: Do not subscribe domain to a DynDNS service
|
help: Do not subscribe domain to a DynDNS service
|
||||||
action: store_true
|
action: store_true
|
||||||
--force-password:
|
|
||||||
help: Use this if you really want to set a weak password
|
|
||||||
action: store_true
|
|
||||||
--force-diskspace:
|
--force-diskspace:
|
||||||
help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem
|
help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem
|
||||||
action: store_true
|
action: store_true
|
||||||
|
|
||||||
|
|
||||||
### tools_update()
|
### tools_update()
|
||||||
update:
|
update:
|
||||||
action_help: YunoHost update
|
action_help: YunoHost update
|
||||||
|
|
|
@ -9,35 +9,45 @@ import time
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.authentication import BaseAuthenticator
|
from moulinette.authentication import BaseAuthenticator
|
||||||
from yunohost.utils.error import YunohostError
|
from yunohost.utils.error import YunohostError
|
||||||
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
|
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
|
||||||
|
|
||||||
|
LDAP_URI = "ldap://localhost:389"
|
||||||
|
ADMIN_GROUP = "cn=admins,ou=groups,dc=yunohost,dc=org"
|
||||||
|
AUTH_DN = "uid={uid},ou=users,dc=yunohost,dc=org"
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(BaseAuthenticator):
|
class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
name = "ldap_admin"
|
name = "ldap_admin"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.uri = "ldap://localhost:389"
|
pass
|
||||||
self.basedn = "dc=yunohost,dc=org"
|
|
||||||
self.admindn = "cn=admin,dc=yunohost,dc=org"
|
|
||||||
|
|
||||||
def _authenticate_credentials(self, credentials=None):
|
def _authenticate_credentials(self, credentials=None):
|
||||||
|
|
||||||
# TODO : change authentication format
|
admins = _get_ldap_interface().search(ADMIN_GROUP, attrs=["memberUid"])[0]["memberUid"]
|
||||||
# to support another dn to support multi-admins
|
|
||||||
|
uid, password = credentials.split(":", 1)
|
||||||
|
|
||||||
|
if uid not in admins:
|
||||||
|
raise YunohostError("invalid_credentials")
|
||||||
|
|
||||||
|
dn = AUTH_DN.format(uid=uid)
|
||||||
|
|
||||||
def _reconnect():
|
def _reconnect():
|
||||||
con = ldap.ldapobject.ReconnectLDAPObject(
|
con = ldap.ldapobject.ReconnectLDAPObject(
|
||||||
self.uri, retry_max=10, retry_delay=0.5
|
LDAP_URI, retry_max=10, retry_delay=0.5
|
||||||
)
|
)
|
||||||
con.simple_bind_s(self.admindn, credentials)
|
con.simple_bind_s(dn, password)
|
||||||
return con
|
return con
|
||||||
|
|
||||||
try:
|
try:
|
||||||
con = _reconnect()
|
con = _reconnect()
|
||||||
except ldap.INVALID_CREDENTIALS:
|
except ldap.INVALID_CREDENTIALS:
|
||||||
raise YunohostError("invalid_password")
|
raise YunohostError("invalid_credentials")
|
||||||
except ldap.SERVER_DOWN:
|
except ldap.SERVER_DOWN:
|
||||||
# ldap is down, attempt to restart it before really failing
|
# ldap is down, attempt to restart it before really failing
|
||||||
logger.warning(m18n.n("ldap_server_is_down_restart_it"))
|
logger.warning(m18n.n("ldap_server_is_down_restart_it"))
|
||||||
|
@ -57,11 +67,8 @@ class Authenticator(BaseAuthenticator):
|
||||||
logger.warning("Error during ldap authentication process: %s", e)
|
logger.warning("Error during ldap authentication process: %s", e)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if who != self.admindn:
|
if who != dn:
|
||||||
raise YunohostError(
|
raise YunohostError(f"Not logged with the appropriate identity ? Found {who}, expected {dn} !?", raw_msg=True)
|
||||||
f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?",
|
|
||||||
raw_msg=True,
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
|
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
|
||||||
if con:
|
if con:
|
||||||
|
|
|
@ -342,7 +342,7 @@ class BackupManager:
|
||||||
# FIXME replace isdir by exists ? manage better the case where the path
|
# FIXME replace isdir by exists ? manage better the case where the path
|
||||||
# exists
|
# exists
|
||||||
if not os.path.isdir(self.work_dir):
|
if not os.path.isdir(self.work_dir):
|
||||||
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid="admin")
|
filesystem.mkdir(self.work_dir, 0o750, parents=True)
|
||||||
elif self.is_tmp_work_dir:
|
elif self.is_tmp_work_dir:
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -358,7 +358,7 @@ class BackupManager:
|
||||||
# we're in /home/yunohost.backup/tmp so that should be okay...
|
# we're in /home/yunohost.backup/tmp so that should be okay...
|
||||||
# c.f. method clean() which also does this)
|
# c.f. method clean() which also does this)
|
||||||
filesystem.rm(self.work_dir, recursive=True, force=True)
|
filesystem.rm(self.work_dir, recursive=True, force=True)
|
||||||
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid="admin")
|
filesystem.mkdir(self.work_dir, 0o750, parents=True)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Backup target management #
|
# Backup target management #
|
||||||
|
@ -1886,7 +1886,7 @@ class CopyBackupMethod(BackupMethod):
|
||||||
|
|
||||||
dest_parent = os.path.dirname(dest)
|
dest_parent = os.path.dirname(dest)
|
||||||
if not os.path.exists(dest_parent):
|
if not os.path.exists(dest_parent):
|
||||||
filesystem.mkdir(dest_parent, 0o700, True, uid="admin")
|
filesystem.mkdir(dest_parent, 0o700, True)
|
||||||
|
|
||||||
if os.path.isdir(source):
|
if os.path.isdir(source):
|
||||||
shutil.copytree(source, dest)
|
shutil.copytree(source, dest)
|
||||||
|
@ -1948,7 +1948,7 @@ class TarBackupMethod(BackupMethod):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not os.path.exists(self.repo):
|
if not os.path.exists(self.repo):
|
||||||
filesystem.mkdir(self.repo, 0o750, parents=True, uid="admin")
|
filesystem.mkdir(self.repo, 0o750, parents=True)
|
||||||
|
|
||||||
# Check free space in output
|
# Check free space in output
|
||||||
self._check_is_enough_free_space()
|
self._check_is_enough_free_space()
|
||||||
|
@ -2632,9 +2632,9 @@ def _create_archive_dir():
|
||||||
if os.path.lexists(ARCHIVES_PATH):
|
if os.path.lexists(ARCHIVES_PATH):
|
||||||
raise YunohostError("backup_output_symlink_dir_broken", path=ARCHIVES_PATH)
|
raise YunohostError("backup_output_symlink_dir_broken", path=ARCHIVES_PATH)
|
||||||
|
|
||||||
# Create the archive folder, with 'admin' as owner, such that
|
# Create the archive folder, with 'admins' as groupowner, such that
|
||||||
# people can scp archives out of the server
|
# people can scp archives out of the server
|
||||||
mkdir(ARCHIVES_PATH, mode=0o750, parents=True, uid="admin", gid="root")
|
mkdir(ARCHIVES_PATH, mode=0o770, parents=True, gid="admins")
|
||||||
|
|
||||||
|
|
||||||
def _call_for_each_path(self, callback, csv_path=None):
|
def _call_for_each_path(self, callback, csv_path=None):
|
||||||
|
|
|
@ -158,15 +158,6 @@ def _get_user_for_ssh(username, attrs=None):
|
||||||
"home_path": root_unix.pw_dir,
|
"home_path": root_unix.pw_dir,
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "admin":
|
|
||||||
admin_unix = pwd.getpwnam("admin")
|
|
||||||
return {
|
|
||||||
"username": "admin",
|
|
||||||
"fullname": "",
|
|
||||||
"mail": "",
|
|
||||||
"home_path": admin_unix.pw_dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
|
# TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
|
|
90
src/tools.py
90
src/tools.py
|
@ -19,10 +19,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
""" yunohost_tools.py
|
|
||||||
|
|
||||||
Specific tools
|
|
||||||
"""
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -67,63 +63,40 @@ def tools_versions():
|
||||||
return ynh_packages_version()
|
return ynh_packages_version()
|
||||||
|
|
||||||
|
|
||||||
def tools_adminpw(new_password, check_strength=True):
|
def tools_rootpw(new_password):
|
||||||
"""
|
|
||||||
Change admin password
|
|
||||||
|
|
||||||
Keyword argument:
|
|
||||||
new_password
|
|
||||||
|
|
||||||
"""
|
|
||||||
from yunohost.user import _hash_user_password
|
from yunohost.user import _hash_user_password
|
||||||
from yunohost.utils.password import assert_password_is_strong_enough
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
import spwd
|
import spwd
|
||||||
|
|
||||||
if check_strength:
|
assert_password_is_strong_enough("admin", new_password)
|
||||||
assert_password_is_strong_enough("admin", new_password)
|
|
||||||
|
new_hash = _hash_user_password(new_password)
|
||||||
|
|
||||||
# UNIX seems to not like password longer than 127 chars ...
|
# UNIX seems to not like password longer than 127 chars ...
|
||||||
# e.g. SSH login gets broken (or even 'su admin' when entering the password)
|
# e.g. SSH login gets broken (or even 'su admin' when entering the password)
|
||||||
if len(new_password) >= 127:
|
if len(new_password) >= 127:
|
||||||
raise YunohostValidationError("admin_password_too_long")
|
raise YunohostValidationError("password_too_long")
|
||||||
|
|
||||||
new_hash = _hash_user_password(new_password)
|
|
||||||
|
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
|
||||||
|
|
||||||
|
# Write as root password
|
||||||
try:
|
try:
|
||||||
ldap.update(
|
hash_root = spwd.getspnam("root").sp_pwd
|
||||||
"cn=admin",
|
|
||||||
{"userPassword": [new_hash]},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("unable to change admin password : %s" % e)
|
|
||||||
raise YunohostError("admin_password_change_failed")
|
|
||||||
else:
|
|
||||||
# Write as root password
|
|
||||||
try:
|
|
||||||
hash_root = spwd.getspnam("root").sp_pwd
|
|
||||||
|
|
||||||
with open("/etc/shadow", "r") as before_file:
|
with open("/etc/shadow", "r") as before_file:
|
||||||
before = before_file.read()
|
before = before_file.read()
|
||||||
|
|
||||||
with open("/etc/shadow", "w") as after_file:
|
with open("/etc/shadow", "w") as after_file:
|
||||||
after_file.write(
|
after_file.write(
|
||||||
before.replace(
|
before.replace(
|
||||||
"root:" + hash_root, "root:" + new_hash.replace("{CRYPT}", "")
|
"root:" + hash_root, "root:" + new_hash.replace("{CRYPT}", "")
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# An IOError may be thrown if for some reason we can't read/write /etc/passwd
|
)
|
||||||
# A KeyError could also be thrown if 'root' is not in /etc/passwd in the first place (for example because no password defined ?)
|
# An IOError may be thrown if for some reason we can't read/write /etc/passwd
|
||||||
# (c.f. the line about getspnam)
|
# A KeyError could also be thrown if 'root' is not in /etc/passwd in the first place (for example because no password defined ?)
|
||||||
except (IOError, KeyError):
|
# (c.f. the line about getspnam)
|
||||||
logger.warning(m18n.n("root_password_desynchronized"))
|
except (IOError, KeyError):
|
||||||
return
|
logger.warning(m18n.n("root_password_desynchronized"))
|
||||||
|
return
|
||||||
logger.info(m18n.n("root_password_replaced_by_admin_password"))
|
|
||||||
logger.success(m18n.n("admin_password_changed"))
|
|
||||||
|
|
||||||
|
|
||||||
def tools_maindomain(new_main_domain=None):
|
def tools_maindomain(new_main_domain=None):
|
||||||
|
@ -189,25 +162,18 @@ def _detect_virt():
|
||||||
def tools_postinstall(
|
def tools_postinstall(
|
||||||
operation_logger,
|
operation_logger,
|
||||||
domain,
|
domain,
|
||||||
|
username,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
password,
|
password,
|
||||||
ignore_dyndns=False,
|
ignore_dyndns=False,
|
||||||
force_password=False,
|
|
||||||
force_diskspace=False,
|
force_diskspace=False,
|
||||||
):
|
):
|
||||||
"""
|
|
||||||
YunoHost post-install
|
|
||||||
|
|
||||||
Keyword argument:
|
|
||||||
domain -- YunoHost main domain
|
|
||||||
ignore_dyndns -- Do not subscribe domain to a DynDNS service (only
|
|
||||||
needed for nohost.me, noho.st domains)
|
|
||||||
password -- YunoHost admin password
|
|
||||||
|
|
||||||
"""
|
|
||||||
from yunohost.dyndns import _dyndns_available
|
from yunohost.dyndns import _dyndns_available
|
||||||
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
from yunohost.utils.dns import is_yunohost_dyndns_domain
|
||||||
from yunohost.utils.password import assert_password_is_strong_enough
|
|
||||||
from yunohost.domain import domain_main_domain
|
from yunohost.domain import domain_main_domain
|
||||||
|
from yunohost.user import user_create
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
# Do some checks at first
|
# Do some checks at first
|
||||||
|
@ -230,10 +196,6 @@ def tools_postinstall(
|
||||||
if not force_diskspace and main_space < 10 * GB:
|
if not force_diskspace and main_space < 10 * GB:
|
||||||
raise YunohostValidationError("postinstall_low_rootfsspace")
|
raise YunohostValidationError("postinstall_low_rootfsspace")
|
||||||
|
|
||||||
# Check password
|
|
||||||
if not force_password:
|
|
||||||
assert_password_is_strong_enough("admin", password)
|
|
||||||
|
|
||||||
# If this is a nohost.me/noho.st, actually check for availability
|
# If this is a nohost.me/noho.st, actually check for availability
|
||||||
if not ignore_dyndns and is_yunohost_dyndns_domain(domain):
|
if not ignore_dyndns and is_yunohost_dyndns_domain(domain):
|
||||||
# Check if the domain is available...
|
# Check if the domain is available...
|
||||||
|
@ -268,8 +230,10 @@ def tools_postinstall(
|
||||||
domain_add(domain, dyndns)
|
domain_add(domain, dyndns)
|
||||||
domain_main_domain(domain)
|
domain_main_domain(domain)
|
||||||
|
|
||||||
|
user_create(username, firstname, lastname, domain, password, admin=True)
|
||||||
|
|
||||||
# Update LDAP admin and create home dir
|
# Update LDAP admin and create home dir
|
||||||
tools_adminpw(password, check_strength=not force_password)
|
tools_rootpw(password)
|
||||||
|
|
||||||
# Enable UPnP silently and reload firewall
|
# Enable UPnP silently and reload firewall
|
||||||
firewall_upnp("enable", no_refresh=True)
|
firewall_upnp("enable", no_refresh=True)
|
||||||
|
|
31
src/user.py
31
src/user.py
|
@ -55,7 +55,7 @@ FIELDS_FOR_IMPORT = {
|
||||||
"groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$",
|
"groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$",
|
||||||
}
|
}
|
||||||
|
|
||||||
FIRST_ALIASES = ["root@", "admin@", "webmaster@", "postmaster@", "abuse@"]
|
ADMIN_ALIASES = ["root@", "admin@", "webmaster@", "postmaster@", "abuse@"]
|
||||||
|
|
||||||
|
|
||||||
def user_list(fields=None):
|
def user_list(fields=None):
|
||||||
|
@ -138,6 +138,7 @@ def user_create(
|
||||||
domain,
|
domain,
|
||||||
password,
|
password,
|
||||||
mailbox_quota="0",
|
mailbox_quota="0",
|
||||||
|
admin=False,
|
||||||
from_import=False,
|
from_import=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
|
@ -146,8 +147,13 @@ def user_create(
|
||||||
from yunohost.utils.password import assert_password_is_strong_enough
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
from yunohost.utils.ldap import _get_ldap_interface
|
from yunohost.utils.ldap import _get_ldap_interface
|
||||||
|
|
||||||
|
# UNIX seems to not like password longer than 127 chars ...
|
||||||
|
# e.g. SSH login gets broken (or even 'su admin' when entering the password)
|
||||||
|
if len(password) >= 127:
|
||||||
|
raise YunohostValidationError("password_too_long")
|
||||||
|
|
||||||
# Ensure sufficiently complex password
|
# Ensure sufficiently complex password
|
||||||
assert_password_is_strong_enough("user", password)
|
assert_password_is_strong_enough("admin" if admin else "user", password)
|
||||||
|
|
||||||
# Validate domain used for email address/xmpp account
|
# Validate domain used for email address/xmpp account
|
||||||
if domain is None:
|
if domain is None:
|
||||||
|
@ -189,9 +195,10 @@ def user_create(
|
||||||
raise YunohostValidationError("system_username_exists")
|
raise YunohostValidationError("system_username_exists")
|
||||||
|
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
aliases = [alias + main_domain for alias in FIRST_ALIASES]
|
# FIXME: should forbit root@any.domain, not just main domain?
|
||||||
|
admin_aliases = [alias + main_domain for alias in ADMIN_ALIASES]
|
||||||
|
|
||||||
if mail in aliases:
|
if mail in admin_aliases:
|
||||||
raise YunohostValidationError("mail_unavailable")
|
raise YunohostValidationError("mail_unavailable")
|
||||||
|
|
||||||
if not from_import:
|
if not from_import:
|
||||||
|
@ -232,10 +239,6 @@ def user_create(
|
||||||
"loginShell": ["/bin/bash"],
|
"loginShell": ["/bin/bash"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# If it is the first user, add some aliases
|
|
||||||
if not ldap.search(base="ou=users,dc=yunohost,dc=org", filter="uid=*"):
|
|
||||||
attr_dict["mail"] = [attr_dict["mail"]] + aliases
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.add("uid=%s,ou=users" % username, attr_dict)
|
ldap.add("uid=%s,ou=users" % username, attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -263,6 +266,8 @@ def user_create(
|
||||||
# Create group for user and add to group 'all_users'
|
# Create group for user and add to group 'all_users'
|
||||||
user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
|
user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
|
||||||
user_group_update(groupname="all_users", add=username, force=True, sync_perm=True)
|
user_group_update(groupname="all_users", add=username, force=True, sync_perm=True)
|
||||||
|
if admin:
|
||||||
|
user_group_update(groupname="admins", add=username, sync_perm=True)
|
||||||
|
|
||||||
# Trigger post_user_create hooks
|
# Trigger post_user_create hooks
|
||||||
env_dict = {
|
env_dict = {
|
||||||
|
@ -416,6 +421,12 @@ def user_update(
|
||||||
change_password = Moulinette.prompt(
|
change_password = Moulinette.prompt(
|
||||||
m18n.n("ask_password"), is_password=True, confirm=True
|
m18n.n("ask_password"), is_password=True, confirm=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# UNIX seems to not like password longer than 127 chars ...
|
||||||
|
# e.g. SSH login gets broken (or even 'su admin' when entering the password)
|
||||||
|
if len(change_password) >= 127:
|
||||||
|
raise YunohostValidationError("password_too_long")
|
||||||
|
|
||||||
# Ensure sufficiently complex password
|
# Ensure sufficiently complex password
|
||||||
assert_password_is_strong_enough("user", change_password)
|
assert_password_is_strong_enough("user", change_password)
|
||||||
|
|
||||||
|
@ -424,7 +435,6 @@ def user_update(
|
||||||
|
|
||||||
if mail:
|
if mail:
|
||||||
main_domain = _get_maindomain()
|
main_domain = _get_maindomain()
|
||||||
aliases = [alias + main_domain for alias in FIRST_ALIASES]
|
|
||||||
|
|
||||||
# If the requested mail address is already as main address or as an alias by this user
|
# If the requested mail address is already as main address or as an alias by this user
|
||||||
if mail in user["mail"]:
|
if mail in user["mail"]:
|
||||||
|
@ -439,6 +449,9 @@ def user_update(
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# FIXME: should also forbid root@any.domain and not just the main domain
|
||||||
|
aliases = [alias + main_domain for alias in ADMIN_ALIASES]
|
||||||
if mail in aliases:
|
if mail in aliases:
|
||||||
raise YunohostValidationError("mail_unavailable")
|
raise YunohostValidationError("mail_unavailable")
|
||||||
|
|
||||||
|
|
|
@ -1144,6 +1144,8 @@ class UserQuestion(Question):
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.default is None:
|
if self.default is None:
|
||||||
|
# FIXME: this code is obsolete with the new admins group
|
||||||
|
# Should be replaced by something like "any first user we find in the admin group"
|
||||||
root_mail = "root@%s" % _get_maindomain()
|
root_mail = "root@%s" % _get_maindomain()
|
||||||
for user in self.choices:
|
for user in self.choices:
|
||||||
if root_mail in user_info(user).get("mail-aliases", []):
|
if root_mail in user_info(user).get("mail-aliases", []):
|
||||||
|
|
Loading…
Add table
Reference in a new issue