Ausführen einer Bash-Skript-Funktion mit Sudo

22

Ich habe ein Skript, das eine Reihe von verschiedenen Dingen ausführt, von denen die meisten keine besonderen Berechtigungen erfordern. Ein bestimmter Abschnitt, den ich in einer Funktion enthalten habe, benötigt jedoch Root-Rechte.

Ich möchte nicht, dass das gesamte Skript als Root ausgeführt wird, und ich möchte diese Funktion mit Root-Rechten innerhalb des Skripts aufrufen können. Wenn nötig nach einem Passwort zu fragen, ist kein Problem, da es sowieso meist interaktiv ist. Wenn ich versuche zu verwenden sudo functionx, erhalte ich jedoch:

sudo: functionx: command not found

Wie ich erwartet hatte, exportmachte das keinen Unterschied. Ich möchte in der Lage sein, die Funktion direkt im Skript auszuführen, anstatt sie aus verschiedenen Gründen aufzubrechen und als separates Skript auszuführen.

Gibt es eine Möglichkeit, meine Funktion für sudo "sichtbar" zu machen, ohne sie zu extrahieren, das entsprechende Verzeichnis zu finden und dann als eigenständiges Skript auszuführen?

Die Funktion ist ungefähr eine Seite lang und enthält mehrere Zeichenfolgen, einige in doppelten Anführungszeichen und einige in einfachen Anführungszeichen. Dies hängt auch von einer Menüfunktion ab, die an anderer Stelle im Hauptskript definiert ist.

Ich würde nur erwarten, dass jemand mit sudo ANY die Funktion ausführen kann, da eine der Aufgaben darin besteht, Kennwörter zu ändern.

BryKKan
quelle
Die Tatsache, dass mehrere Funktionen beteiligt sind, macht es noch komplizierter und fehleranfälliger. Sie müssen jetzt alle diese Abhängigkeiten (und alle Abhängigkeiten, falls vorhanden, bis zu einer beliebigen Tiefe von mehreren Ebenen) finden, einschließlich aller anderen Funktionen, die die Menüfunktion aufruft, und auch declaredieser Funktionen .
cas
Einverstanden, und ich muss möglicherweise nur in die Kugel beißen und sie aufbrechen (und mein Bestes geben, um den Pfad, von dem aus sie ausgeführt wurde, genau zu bestimmen und zu hoffen, dass der Endbenutzer die Dateien zusammenhält), wenn es keine besseren Alternativen gibt.
BryKKan

Antworten:

19

Ich gebe zu, dass es keinen einfachen, intuitiven Weg gibt, und das ist ein bisschen kitschig. Aber du kannst es so machen:

function hello()
{
    echo "Hello!"
}

# Test that it works.
hello

FUNC=$(declare -f hello)
sudo bash -c "$FUNC; hello"

Oder einfacher:

sudo bash -c "$(declare -f hello); hello"

Für mich geht das:

$ bash --version
GNU bash, version 4.3.42(1)-release (x86_64-apple-darwin14.5.0)
$ hello
Hello!
$
$ FUNC=$(declare -f hello)
$ sudo bash -c "$FUNC; hello"
Hello!

Grundsätzlich declare -fwird der Inhalt der Funktion zurückgegeben, die Sie dann bash -cinline übergeben.

Wenn Sie alle Funktionen aus der äußeren Instanz von bash exportieren möchten, wechseln Sie FUNC=$(declare -f hello)zu FUNC=$(declare -f).

Bearbeiten

Informationen zu den Kommentaren zum Zitieren finden Sie in diesem Beispiel:

$ hello()
> {
> echo "This 'is a' test."
> }
$ declare -f hello
hello ()
{
    echo "This 'is a' test."
}
$ FUNC=$(declare -f hello)
$ sudo bash -c "$FUNC; hello"
Password:
This 'is a' test.
Wille
quelle
1
Dies funktioniert nur aus Versehen, da echo "Hello!"es praktisch dasselbe ist wie echo Hello!(dh doppelte Anführungszeichen machen für diesen speziellen Echobefehl keinen Unterschied). In vielen / den meisten anderen Fällen wird der bash -cBefehl wahrscheinlich durch doppelte Anführungszeichen in der Funktion unterbrochen.
cas
1
Dies beantwortet die ursprüngliche Frage. Wenn ich keine bessere Lösung erhalte, akzeptiere ich sie. Es unterbricht jedoch meine spezielle Funktion (siehe meine Bearbeitung), da es von Funktionen abhängt, die an anderer Stelle im Skript definiert sind.
BryKKan
1
Ich habe heute Nachmittag einige Tests durchgeführt ( bash -xcund nicht nur bash -c), und es scheint bashklug genug zu sein, Dinge in dieser Situation erneut zu zitieren, selbst in dem Maße, in dem doppelte Anführungszeichen durch einfache Anführungszeichen ersetzt und bei Bedarf 'auf '\''geändert werden. Ich bin mir sicher, dass es einige Fälle geben wird, die nicht behandelt werden können, aber es funktioniert definitiv für zumindest einfache und mäßig komplexe Fälle - zB tryfunction hello() { filename="this is a 'filename' with single quotes and spaces" ; echo "$filename" ; } ; FUNC=$(declare -f hello) ; bash -xc "$FUNC ; hello"
cas
4
@cas gibt declare -fdie Funktionsdefinition auf eine Weise aus, die von bash erneut analysiert werden kann. Dies funktioniert bash -c "$(declare -f)" also ordnungsgemäß (vorausgesetzt, die äußere Shell ist ebenfalls bash). Das Beispiel, das Sie gepostet haben, zeigt, dass es korrekt funktioniert - wo die Anführungszeichen geändert wurden, befindet sich im Trace , da Bash Spuren in Shell-Syntax ausgibt, z. B. trybash -xc 'echo "hello world"'
Gilles 'SO - hör auf, böse zu sein'
3
Hervorragende Antwort. Ich implementierte Ihre Lösung - Ich mag darauf hinweisen , dass Sie das Skript selbst aus dem Skript importieren können, vorausgesetzt , Sie nistet es innerhalb eines bedingten , die prüfen , ob gegen sudo yourFunctiongefunden werden (Sie sonst einen Segmentierungsfehler aus der Rekursion erhalten)
GrayedFox
5

Das "Problem" besteht darin, dass sudodie Umgebung gelöscht wird (mit Ausnahme einiger zulässiger Variablen) und einige Variablen auf vordefinierte sichere Werte gesetzt werden, um sich vor Sicherheitsrisiken zu schützen. Mit anderen Worten, dies ist eigentlich kein Problem. Es ist ein Feature.

Wenn Sie beispielsweise PATH auf einen vordefinierten Wert setzen PATH="/path/to/myevildirectory:$PATH"und sudonicht setzen, sucht jedes Skript, das nicht den vollständigen Pfadnamen für ALLE von ihm ausgeführten Befehle angibt (dh die meisten Skripte), /path/to/myevildirectoryvor jedem anderen Verzeichnis. Wenn Sie Befehle wie lsoder grepandere gebräuchliche Tools einfügen, können Sie auf einfache Weise das tun, was Sie möchten.

Der einfachste / beste Weg ist, die Funktion als Skript neu zu schreiben und irgendwo im Pfad zu speichern (oder den vollständigen Pfad zum Skript in der sudoBefehlszeile anzugeben - was Sie sowieso tun müssen, es sei denn, dies sudoist so konfiguriert, dass Sie dies zulassen JEDEN Befehl als root ausführen) und mit ausführbar machenchmod +x /path/to/scriptname.sh

Das Umschreiben einer Shell-Funktion als Skript ist so einfach wie das Speichern der Befehle in der Funktionsdefinition in eine Datei (ohne die Zeilen function ..., {und }).

cas
quelle
Dies beantwortet die Frage in keiner Weise. Er möchte es ausdrücklich vermeiden, es in ein Drehbuch zu schreiben.
Wird
1
sudo -EVermeidet auch das Räumen der Umgebung.
Wird
Ich verstehe bis zu einem gewissen Grad, warum es passiert. Ich hatte gehofft, dass es Mittel gibt, um dieses Verhalten vorübergehend außer Kraft zu setzen. An anderer Stelle wurde eine Option -E erwähnt, die in diesem Fall jedoch nicht funktionierte. Obwohl ich die Erklärung, wie man es zu einem eigenständigen Skript macht, zu schätzen weiß, beantwortet das die Frage leider nicht, weil ich ein Mittel wollte, um das zu vermeiden. Ich habe keine Kontrolle darüber, wo der Endbenutzer das Skript platziert, und ich möchte vermeiden, dass sowohl hartcodierte Verzeichnisse als auch das Lied und der Tanz genau bestimmen, wo das Hauptskript ausgeführt wurde.
BryKKan
Es spielt keine Rolle, ob das der OP verlangt oder nicht. Wenn das, was er will, entweder nicht funktioniert oder nur durch extrem unsichere Handlungen zur Arbeit gebracht werden kann, muss man ihnen dies mitteilen und sie mit einer Alternative ausstatten - auch wenn die Alternative etwas ist, von dem sie ausdrücklich sagten, dass sie es nicht wollen (weil manchmal ist das der einzige oder der beste Weg, es sicher zu machen). Es wäre unverantwortlich, jemandem zu sagen, wie er sich in den Fuß schießen soll, ohne ihn vor den wahrscheinlichen Folgen zu warnen, wenn er eine Waffe auf seine Füße richtet und den Abzug drückt.
cas
1
@cas Das stimmt. Es ist unter bestimmten Umständen eine akzeptable Antwort, dass dies nicht sicher durchgeführt werden kann. Siehe meine letzte Bearbeitung. Ich wäre gespannt, ob Ihre Meinung zu den Sicherheitsaspekten gleich ist.
BryKKan
3

Ich habe dazu meine eigene SudoBash-Funktion geschrieben, die Funktionen und Aliase aufruft:

function Sudo {
        local firstArg=$1
        if [ $(type -t $firstArg) = function ]
        then
                shift && command sudo bash -c "$(declare -f $firstArg);$firstArg $*"
        elif [ $(type -t $firstArg) = alias ]
        then
                alias sudo='\sudo '
                eval "sudo $@"
        else
                command sudo "$@"
        fi
}
SebMa
quelle
2

Sie können Funktionen und Aliase kombinieren

Beispiel:

function hello_fn() {
    echo "Hello!" 
}

alias hello='bash -c "$(declare -f hello_fn); hello_fn"' 
alias sudo='sudo '

dann sudo helloklappt es

hbt
quelle
1

Hier ist eine Variation von Wills Antwort . Es beinhaltet einen zusätzlichen catProzess, bietet aber den Komfort von heredoc. Kurz gesagt, es geht so:

f () 
{
    echo ok;
}

cat <<EOS | sudo bash
$(declare -f f)
f
EOS

Wenn Sie mehr zum Nachdenken anregen möchten, versuchen Sie Folgendes:

#!/bin/bash

f () 
{ 
    x="a b"; 
    menu "$x"; 
    y="difficult thing"; 
    echo "a $y to parse"; 
}

menu () 
{
    [ "$1" == "a b" ] && 
    echo "here's the menu"; 
}

cat <<EOS | sudo bash
$(declare -f f)
$(declare -f menu)
f
EOS

Die Ausgabe ist:

here's the menu
a difficult thing to pass

Hier haben wir die menuFunktion, die der in der Frage entspricht, die "an anderer Stelle im Hauptskript definiert ist". Wenn das "anderswo" bedeutet, dass seine Definition bereits zu diesem Zeitpunkt gelesen wurde, als die Funktion "fordernd sudo" ausgeführt wird, ist die Situation analog. Aber es könnte noch nicht gelesen worden sein. Möglicherweise gibt es eine andere Funktion, die ihre Definition noch auslöst. In diesem Fall declare -f menumuss es durch etwas Raffinierteres ersetzt oder das gesamte Skript so korrigiert werden, dass die menuFunktion bereits deklariert ist.

Tomasz
quelle
Sehr interessant. Ich werde es irgendwann ausprobieren müssen. Und ja, die menuFunktion wäre vor diesem Punkt deklariert worden, wie fes aus einem Menü aufgerufen wird.
BryKKan
0

Angenommen, Ihr Skript ist entweder (a) eigenständig oder (b) kann seine Komponenten basierend auf seinem Speicherort beziehen (anstatt sich zu erinnern, wo sich Ihr Ausgangsverzeichnis befindet), können Sie Folgendes tun:

  • Verwenden Sie den $0Pfadnamen für das Skript sudound geben Sie eine Option ein, die vom Skript überprüft wird, um die Kennwortaktualisierung aufzurufen. Solange Sie darauf angewiesen sind, das Skript im Pfad zu finden (und nicht nur auszuführen ./myscript), sollten Sie einen absoluten Pfadnamen in erhalten $0.
  • Da das sudoSkript ausgeführt wird, hat es Zugriff auf die Funktionen, die es im Skript benötigt.
  • Am Anfang des Skripts (nach den Funktionsdeklarationen) prüft das Skript uid, ob es als Root- Benutzer ausgeführt wurde, und stellt fest, dass die Option festgelegt ist, dass das Kennwort aktualisiert werden soll Das.

Skripte können sich aus verschiedenen Gründen wiederholen: Das Ändern von Berechtigungen ist eine davon.

Thomas Dickey
quelle