2010-04-23 10 views
40

Chciałbym, aby program w języku Python zaczął nasłuchiwać na porcie 80, ale po tym uruchomieniu bez uprawnień root. Czy istnieje sposób, aby opuścić root lub uzyskać port 80 bez niego?Upuszczanie uprawnień roota w Pythonie

+3

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

+4

Na współczesnym Linuxie potrzebujesz tylko możliwości CAP_NET_ BIND_SERVICE, aby powiązać port 80, NIE musisz być rootem, nawet przy uruchomieniu aplikacji. Możliwości to standard POSIX, 1003.1e, czyli partycjonowanie wszystkich potężnych uprawnień roota w zestaw odrębnych uprawnień. Patrz: python-cap-ng i/sbin/setcap,/sbin/getcap (te są równoważne chmod setuid i ls -l) –

+0

Dla python2 i być może inni tłumacze, zyskując możliwości jest częścią ty chcesz być ostrożny z - libcap-ng może zrzucić ograniczenia, ale nie daje im. Ta odpowiedź na pytanie, o którym wspomniał Ian, jest względnie bezpiecznym sposobem na rozdzielenie jednego pułapu w czasie dla konkretnych projektów: http://stackoverflow.com/a/21895123/1724577 – duanev

Odpowiedz

50

Nie będzie można otworzyć serwera na porcie 80 bez uprawnień roota, jest to ograniczenie na poziomie systemu operacyjnego. Jedynym rozwiązaniem jest porzucenie uprawnień roota po otwarciu portu.

Oto możliwe rozwiązanie polegające na zrzucaniu uprawnień roota w Pythonie: Dropping privileges in Python. Jest to dobre rozwiązanie w ogólności, ale musisz również dodać do funkcji funkcję os.setgroups([]), aby zagwarantować, że członkostwo grupy użytkownika root nie zostanie zachowane.

Skopiowałem i trochę wyczyściłem kod, usunąłem rejestrowanie i procedury obsługi wyjątków, więc pozostało ci właściwe obsłużenie OSError (zostanie ono wysłane, gdy proces nie będzie mógł zmienić jego efektywnego UID lub 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

Należy pamiętać, że katalog HOME nadal będzie "/ root', a nie '/ home/uid_name', a nazwa uid_name nie będzie w stanie nic zrobić z' ~/', która rozwinie się do'/root/'. Może to wpłynąć na moduły takie jak matplotlib przechowujące dane konfiguracyjne w katalogu HOME. 'authbind' wydaje się właściwym sposobem na rozwiązanie tego problemu. –

+1

A zmienna HOME nie będzie jedyną, która wciąż myśli, że bieżący użytkownik jest rootem. –

+1

* "Nie można otworzyć serwera na porcie 80 bez uprawnień root'a ..." * - To niekoniecznie musi być prawda (może już?). Zobacz także: Czy zezwolić procesowi spoza root na powiązanie z portem 80 i 443?] (Https://superuser.com/q/710253/173513) na SuperUser i [Czy jest sposób, aby procesy inne niż root wiązały się z "uprzywilejowanymi" "Porty na Linuksie?" (Https://stackoverflow.com/q/413807/608639) – jww

12

Polecam używanie authbind aby uruchomić program Pythona, więc nic z tego nie ma uruchomić jako root.

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

+0

Przykład użycia 'authbind' - http://goo.gl/fxFde6 - Zamień NodeJS na cokolwiek chcesz (np. : python) – starlocke

3
  1. Systemd może zrobić dla ciebie, jeśli uruchomić program przez Systemd, Systemd może przekazać wyłączyć już otwarte gniazdo słuchając go, a to może także aktywować swój program na pierwszym połączeniu . i nawet nie musisz go dememonizować.

  2. Jeśli zamierzasz korzystać z samodzielnego podejścia, potrzebujesz możliwości CAP_NET_BIND_SERVICE (sprawdź stronę podręcznika możliwości). Można to zrobić programując program, korzystając z odpowiedniego narzędzia wiersza poleceń lub ustawiając aplikację (1) na suid root (2) start up (3) nasłuchuj przywilejów/możliwości przywracania portu (4) natychmiast .

Pamiętaj, że korzeń suid programy pochodzą z wielu względów bezpieczeństwa (czyste i bezpieczne środowisko, umask, przywileje, rlimits, wszystkie te rzeczy są rzeczy, które program będzie musiał skonfigurować poprawnie). Jeśli możesz użyć czegoś takiego jak systemd, tym lepiej.

2

Większość z nich działa, chyba że musisz poprosić o gniazdo po wykonaniu innych czynności, których nie chcesz być superużytkownikiem.

Zrobiłem projekt o nazwie tradesocket jakiś czas temu. Pozwala na przekazywanie do gniazdka w systemie posix między procesami. To, co robię, polega na wyłapywaniu procesu na początku, który pozostaje superużytkownikiem, a reszta procesu spada w uprawnienia, a następnie żąda gniazda od drugiego.

7

Nie jest dobrym pomysłem, aby poprosić użytkownika o podanie swojej nazwy użytkownika i grupy, kiedy tylko chcę upuścić uprawnienia. Oto nieco zmodyfikowana wersja kodu Tamása, która zrzuci przywileje i przełączy się na użytkownika, który zainicjował polecenie sudo. Zakładam, że używasz sudo (jeśli nie, użyj kodu Tamás'a).

#!/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

Poniżej znajduje się dalsze dostosowanie Tamás's answer, z następującymi zmianami:

  • Użyj python-prctl module do spadku możliwości systemu Linux do określonej listy możliwości zachować.
  • Użytkownik może być opcjonalnie przekazany jako parametr (domyślnie szuka użytkownika, który prowadził sudo).
  • Ustawia wszystkie grupy użytkowników i HOME.
  • Opcjonalnie zmienia katalog.

(Jestem stosunkowo nowy w użyciu tej funkcji, jednak, więc może coś przeoczyć. To może nie działać na starszych jąder (< 3.8) lub jądra z możliwościami systemu plików wyłączone.)

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) 

może być stosowany w sposób następujący:

drop_privileges(user='www', rundir='~', caps=[prctl.CAP_NET_BIND_SERVICE]) 
Powiązane problemy