2010-04-23 5 views
40

Je voudrais qu'un programme Python commence à écouter sur le port 80, mais après cela, exécute sans autorisations root. Existe-t-il un moyen de supprimer root ou d'obtenir le port 80 sans lui?Suppression des autorisations root en Python

+3

http://stackoverflow.com/questions/413807/is-there-a-way-for-non-root-processes-to-bind-to-privileged -ports-1024-on-li –

+4

Sur Linux moderne, vous n'avez besoin que de capacités CAP_NET_ BIND_SERVICE pour lier au port 80, vous n'avez pas besoin d'être root, même au démarrage de l'application. Les capacités sont un standard POSIX, 1003.1e, qui est un partitionnement du privilège racine tout puissant en un ensemble de privilèges distincts. Voir: python-cap-ng et/sbin/setcap,/sbin/getcap (ceux-ci sont équivalentes à chmod setuid et ls -l) –

+0

Pour python2 et peut-être d'autres interprètes, acquérir des capacités est la partie que vous voulez faire attention avec - libcap-ng peut laisser tomber les majuscules mais il ne les accorde pas. Cette réponse à la question posée par Ian est un moyen relativement sûr de distribuer un chapeau à la fois pour des projets spécifiques: http://stackoverflow.com/a/21895123/1724577 – duanev

Répondre

50

Vous ne serez pas en mesure d'ouvrir un serveur sur le port 80 sans privilèges root, ceci est une restriction au niveau du système d'exploitation. La seule solution est donc de supprimer les privilèges root après avoir ouvert le port.

Voici une solution possible pour supprimer les privilèges root en Python: Dropping privileges in Python. C'est une bonne solution en général, mais vous devrez également ajouter os.setgroups([]) à la fonction pour vous assurer que l'appartenance au groupe de l'utilisateur root n'est pas conservée. J'ai copié et nettoyé le code un petit peu, et supprimé la journalisation et les gestionnaires d'exceptions, il est donc laissé à vous de gérer correctement OSError (il sera levé lorsque le processus n'est pas autorisé à changer son UID effectif ou GID):

import os, pwd, grp 

def drop_privileges(uid_name='nobody', gid_name='nogroup'): 
    if os.getuid() != 0: 
     # We're not root so, like, whatever dude 
     return 

    # Get the uid/gid from the name 
    running_uid = pwd.getpwnam(uid_name).pw_uid 
    running_gid = grp.getgrnam(gid_name).gr_gid 

    # Remove group privileges 
    os.setgroups([]) 

    # Try setting the new uid/gid 
    os.setgid(running_gid) 
    os.setuid(running_uid) 

    # Ensure a very conservative umask 
    old_umask = os.umask(077) 
+1

Gardez à l'esprit que le répertoire HOME sera toujours '/ root' et non '/ home/uid_name', et que uid_name ne pourra rien faire avec' ~/', qui sera ensuite développé en'/root/'. Cela peut affecter des modules comme matplotlib qui stockent des données de configuration dans le répertoire HOME. 'authbind' semble être la bonne façon de gérer ce problème. –

+1

Et la variable HOME ne sera pas la seule à penser que l'utilisateur actuel est root. –

+1

* "Vous ne serez pas en mesure d'ouvrir un serveur sur le port 80 sans privilèges root ..." * - Ce n'est pas nécessairement vrai (peut-être plus?). Voir aussi [Autoriser le processus non root à se lier aux ports 80 et 443?] (Https://superuser.com/q/710253/173513) sur SuperUser et [Existe-t-il un moyen pour les processus non root de se lier à "privilégié"? "Ports sur Linux?] (Https://stackoverflow.com/q/413807/608639) – jww

12

Je recommande d'utiliser authbind pour démarrer votre programme Python, donc rien ne doit fonctionner en tant que root.

https://en.wikipedia.org/wiki/Authbind

+0

Un exemple d'utilisation de' authbind' - http://goo.gl/fxFde6 - Remplacez NodeJS par ce que vous voulez (ex : python) – starlocke

3
  1. systemd peut le faire pour vous, si vous commencez votre programme par systemd, systemd peut remettre de la prise d'écoute déjà ouverte à lui, et il peut également activer votre programme sur la première connexion . et vous n'avez même pas besoin de le démoniser. Si vous souhaitez utiliser l'approche autonome, vous avez besoin de la fonction CAP_NET_BIND_SERVICE (consultez la page man de capabilities). Cela peut être fait programme par programme avec l'outil de ligne de commande correct, ou en faisant votre application (1) être suid root (2) démarrer (3) écouter le port (4) abandonner les privilèges/capacités immédiatement .

Rappelez-vous que les programmes suid root viennent avec beaucoup de considérations de sécurité (environnement propre et sûr, umask, privilèges, rlimits, toutes ces choses sont des choses que votre programme va devoir mettre en place correctement). Si vous pouvez utiliser quelque chose comme systemd, tant mieux.

2

La plupart de cette fonctionnalité fonctionne à moins que vous ne deviez demander le socket après avoir fait d'autres tâches que vous ne voulez pas être super-utilisateur.

J'ai fait un projet appelé tradesocket il y a quelque temps. Il vous permet de passer des sockets d'avant en arrière sur un système Posix entre les processus. Ce que je fais est de faire tourner un processus au début qui reste super-utilisateur, et le reste du processus descend dans les autorisations, puis demande le socket de l'autre.

7

Ce n'est pas une bonne idée de demander à l'utilisateur d'entrer son nom d'utilisateur et son groupe chaque fois que j'ai besoin de supprimer des privilèges. Voici une version légèrement modifiée du code de Tamás qui supprimera les privilèges et passera à l'utilisateur qui a initié la commande sudo. Je suppose que vous utilisez sudo (sinon, utilisez le code de Tamás).

#!/usr/bin/env python3 

import os, pwd, grp 

#Throws OSError exception (it will be thrown when the process is not allowed 
#to switch its effective UID or GID): 
def drop_privileges(): 
    if os.getuid() != 0: 
     # We're not root so, like, whatever dude 
     return 

    # Get the uid/gid from the name 
    user_name = os.getenv("SUDO_USER") 
    pwnam = pwd.getpwnam(user_name) 

    # Remove group privileges 
    os.setgroups([]) 

    # Try setting the new uid/gid 
    os.setgid(pwnam.pw_gid) 
    os.setuid(pwnam.pw_uid) 

    #Ensure a reasonable umask 
    old_umask = os.umask(0o22) 


#Test by running... 
#./drop_privileges 
#sudo ./drop_privileges 
if __name__ == '__main__': 
    print(os.getresuid()) 
    drop_privileges() 
    print(os.getresuid()) 
1

Voici une nouvelle adaptation de Tamás's answer, avec les modifications suivantes:

  • Utilisez le python-prctl module d'abandonner les capacités Linux à une liste des capacités pour préserver.
  • L'utilisateur peut éventuellement être passé en paramètre (il recherche par défaut l'utilisateur qui a exécuté sudo).
  • Il définit tous les groupes d'utilisateurs et .
  • Il change éventuellement de répertoire.

(je suis relativement nouveau d'utiliser cette fonctionnalité, cependant, donc je peut-être manqué quelque chose. Il ne fonctionne pas sur les anciens noyaux (< 3.8) ou des noyaux avec des capacités de systèmes de fichiers désactivés.)

def drop_privileges(user=None, rundir=None, caps=None): 
    import os 
    import pwd 

    if caps: 
     import prctl 

    if os.getuid() != 0: 
     # We're not root 
     raise PermissionError('Run with sudo or as root user') 

    if user is None: 
     user = os.getenv('SUDO_USER') 
     if user is None: 
      raise ValueError('Username not specified') 
    if rundir is None: 
     rundir = os.getcwd() 

    # Get the uid/gid from the name 
    pwnam = pwd.getpwnam(user) 

    if caps: 
     prctl.securebits.keep_caps=True 
     prctl.securebits.no_setuid_fixup=True 

    # Set user's group privileges 
    os.setgroups(os.getgrouplist(pwnam.pw_name, pwnam.pw_gid)) 

    # Try setting the new uid/gid 
    os.setgid(pwnam.pw_gid) 
    os.setuid(pwnam.pw_uid) 

    os.environ['HOME'] = pwnam.pw_dir 

    os.chdir(os.path.expanduser(rundir)) 

    if caps: 
     prctl.capbset.limit(*caps) 
     try: 
      prctl.cap_permitted.limit(*caps) 
     except PermissionError: 
      pass 
     prctl.cap_effective.limit(*caps) 

    #Ensure a reasonable umask 
    old_umask = os.umask(0o22) 

Il peut être utilisé comme suit:

drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE]) 
Questions connexes