web-dev-qa-db-fra.com

Écrire dans un fichier avec les privilèges Sudo en Python

Le code suivant génère une erreur s'il est exécuté par un utilisateur non root pour un fichier appartenant à root, même lorsque l'utilisateur non root dispose des privilèges Sudo:

try:
  f = open(filename, "w+")
except IOError:
  sys.stderr.write('Error: Failed to open file %s' % (filename))
f.write(response + "\n" + new_line)
f.close()

Existe-t-il un moyen d'exécuter open(filename, "w+") avec les privilèges Sudo, ou une fonction alternative qui fait cela?

12
user2824889

Vous avez quelques options:

  • Exécutez votre script en tant que root ou avec Sudo
  • Définissez le bit setuid et demandez à root de posséder le script (bien que sur de nombreux systèmes, cela ne fonctionnera pas avec les scripts, puis le script pourra être appelé par n'importe qui)
  • Détectez que vous ne vous exécutez pas en tant que root (os.geteuid() != 0), puis appelez-vous avec Sudo infront (qui demandera à l'utilisateur d'entrer son mot de passe) et quittez:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['Sudo', 'python3', *sys.argv])
    sys.exit()

L'appeler ressemble à ceci:

$ python3 be_root.py
We're not root.
Password:
We're root!
10
L3viathan

TL; DR:

réponse de L3viathan a un SynaxError Edit: fonctionne très bien sur Python 3.5+. Voici une version pour Python 3.4.3 (distribuée par défaut sur Ubuntu 16.04) et ci-dessous:

if os.geteuid() == 0:
    # do root things
else:
    subprocess.call(['Sudo', 'python3'] + sys.argv)  # modified

Cependant, j'ai opté pour une approche différente car cela a simplifié le code qui l'entoure dans mon cas d'utilisation:

if os.geteuid() != 0:
    os.execvp('Sudo', ['Sudo', 'python3'] + sys.argv)
# do root things

Explication

réponse de L3viathan dépend de PEP 448 , qui a été inclus dans Python 3.5 et a introduit des contextes supplémentaires où l'expansion des arguments en étoile est autorisée. Pour Python 3.4 et versions antérieures, la concaténation de liste peut être utilisée pour faire la même chose:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['Sudo', 'python3'] + sys.argv)  # modified

Mais note: subprocess.call() lance un processus enfant, ce qui signifie qu'après que root aura fini d'exécuter le script, l'utilisateur d'origine continuera d'exécuter son script en tant que bien. Cela signifie que vous devez placer la logique élevée dans un côté d'un bloc if/else Afin que lorsque le script d'origine se termine, il n'essaie pas d'exécuter la logique qui nécessite une élévation (l'exemple de L3viathan fait cela).

Ce n'est pas nécessairement une mauvaise chose - cela signifie que la logique normale/élevée peut être écrite dans le même script et bien séparée - mais ma tâche nécessite une racine pour toute la logique. Je ne voulais pas perdre un niveau d'indentation si l'autre bloc devait être vide, et je n'avais pas réalisé comment l'utilisation d'un processus enfant affecterait les choses, alors j'ai essayé ceci:

import os
import sys
import subprocess

if os.geteuid() != 0:
    subprocess.call(['Sudo', 'python3'] + sys.argv)

# do things that require root

... et il s'est cassé bien sûr, car après que root a été fait, l'utilisateur régulier a repris l'exécution de son script et a essayé d'exécuter les instructions qui nécessitaient root.

Puis j'ai trouvé cet Gist recommandant os.execvp() - qui remplace le processus en cours au lieu de lancer un enfant:

import os
import sys

if os.geteuid() != 0:
    os.execvp('Sudo', ['Sudo', 'python3'] + sys.argv)  # final version

# do things that require root

Cela semble se comporter comme prévu, enregistre un niveau d'indentation et 3 lignes de code.

Avertissement: je ne connaissais pas os.execvp() il y a dix minutes, et je ne sais encore rien sur les pièges ou subtilités possibles autour de son utilisation. YMMV.

7
Peter Henry

Votre script est limité aux autorisations avec lesquelles il est exécuté car vous ne pouvez pas modifier les utilisateurs sans avoir déjà les privilèges root.

Comme l'a dit Rob, la seule façon de le faire sans modifier vos autorisations de fichier est d'exécuter avec Sudo.

Sudo python ./your_file.py
1
Matt Olan

Avoir la possibilité d'utiliser Sudo ne vous donne aucun privilège si vous ne l'utilisez pas réellement. Donc, comme d'autres l'ont suggéré, vous devriez probablement démarrer votre programme en utilisant Sudo. Mais si vous n'aimez pas cette idée (je ne vois aucune raison à cela), vous pouvez faire un autre tour.

Votre script peut vérifier s'il est exécuté avec des privilèges root ou s'il fonctionne uniquement avec des privilèges utilisateur. Le script peut réellement s'exécuter avec des privilèges plus élevés. Voici un exemple (veuillez noter que le stockage du mot de passe dans le code source n'est pas une bonne idée).

import os.path
import subprocess

password_for_Sudo = 'pass'

def started_as_root():
    if subprocess.check_output('whoami').strip() == 'root':
        return True
    return False

def runing_with_root_privileges():
    print 'I have the power!'

def main():
    if started_as_root():
        print 'OK, I have root privileges. Calling the function...'
        runing_with_root_privileges()
    else:
        print "I'm just a user. Need to start new process with root privileges..."
        current_script = os.path.realpath(__file__)
        os.system('echo %s|Sudo -S python %s' % (password_for_Sudo, current_script))

if __name__ == '__main__':
    main()

Production:

$ python test.py
I'm just a user. Need to start new process with root privileges...
OK, I have root privileges. Calling the function...
I have the power!

$ Sudo python test.py
OK, I have root privileges.
Calling the function...
I have the power!
1
Adam

J'aime la réponse de L3viathan. J'ajouterais une quatrième option: utilisez subprocess.Popen () pour exécuter une commande sh pour écrire le fichier. Quelque chose comme subprocess.Popen('Sudo echo \'{}\' > {}'.format(new_line, filename)).

0
portusato