Exécution d'une fonction de script Bash avec Sudo

22

J'ai un script qui fait un certain nombre de choses différentes, dont la plupart ne nécessitent aucun privilège spécial. Cependant, une section spécifique, que j'ai contenue dans une fonction, a besoin des privilèges root.

Je ne souhaite pas exiger que le script entier s'exécute en tant que root, et je veux pouvoir appeler cette fonction, avec les privilèges root, à partir du script. Demander un mot de passe si nécessaire n'est pas un problème car il est surtout interactif de toute façon. Cependant, lorsque j'essaie d'utiliser sudo functionx, j'obtiens:

sudo: functionx: command not found

Comme je m'y attendais, cela exportn'a pas fait de différence. J'aimerais pouvoir exécuter la fonction directement dans le script plutôt que de la décomposer et de l'exécuter en tant que script distinct pour un certain nombre de raisons.

Existe-t-il un moyen de rendre ma fonction "visible" pour sudo sans l'extraire, trouver le répertoire approprié, puis l'exécuter en tant que script autonome?

La fonction concerne une page elle-même et contient plusieurs chaînes, certaines entre guillemets doubles et d'autres entre guillemets simples. Il dépend également d'une fonction de menu définie ailleurs dans le script principal.

Je m'attendrais seulement à ce que quelqu'un avec sudo ANY puisse exécuter la fonction, car l'une des choses qu'il fait est de changer les mots de passe.

BryKKan
la source
Le fait qu'il y ait plusieurs fonctions impliquées le rend encore plus compliqué et sujet à l'échec. Vous devez maintenant trouver toutes ces dépendances (et toutes leurs dépendances aussi, le cas échéant ... à des niveaux aussi profonds), y compris toutes les autres fonctions que la fonction de menu pourrait appeler et declareelles aussi.
cas
D'accord, et je devrai peut-être mordre la balle et la casser (et faire de mon mieux pour déterminer avec précision le chemin à partir duquel elle a été exécutée, et espérer que l'utilisateur final conserve les fichiers ensemble) s'il n'y a pas de meilleures alternatives.
BryKKan

Réponses:

19

J'admets qu'il n'y a pas de moyen simple et intuitif de le faire, et c'est un peu hackey. Mais vous pouvez le faire comme ceci:

function hello()
{
    echo "Hello!"
}

# Test that it works.
hello

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

Ou plus simplement:

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

Ça marche pour moi:

$ 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!

Fondamentalement, declare -frenvoie le contenu de la fonction, que vous passez ensuite en bash -cligne.

Si vous souhaitez exporter toutes les fonctions de l'instance externe de bash, passez FUNC=$(declare -f hello)à FUNC=$(declare -f).

Éditer

Pour répondre aux commentaires sur les devis, consultez cet exemple:

$ 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.
Volonté
la source
1
Cela ne fonctionne que par accident, car il echo "Hello!"est en fait identique à echo Hello!(c'est-à-dire que les guillemets doubles ne font aucune différence pour cette commande d'écho particulière). Dans de nombreuses circonstances / la plupart des autres, les guillemets doubles dans la fonction sont susceptibles de casser la bash -ccommande.
cas
1
Cela répond à la question d'origine, donc si je n'obtiens pas de meilleure solution, je l'accepterai. Cependant, cela casse ma fonction particulière (voir ma modification) car elle dépend de fonctions définies ailleurs dans le script.
BryKKan
1
je l' ai fait quelques essais plus tôt cet après - midi ( en utilisant bash -xcplutôt que bash -c) et il ressemble bashest assez intelligent pour les choses re-citation dans cette situation, même dans la mesure de remplacer les guillemets doubles avec des guillemets simples et changer 'de '\''si nécessaire. Je suis sûr qu'il y aura des cas qu'il ne pourra pas gérer, mais cela fonctionne certainement pour au moins les cas simples et moyennement complexes - par exemple, essayezfunction hello() { filename="this is a 'filename' with single quotes and spaces" ; echo "$filename" ; } ; FUNC=$(declare -f hello) ; bash -xc "$FUNC ; hello"
cas
4
@cas declare -fimprime la définition de la fonction d'une manière qui peut être analysée à nouveau par bash, donc bash -c "$(declare -f)" cela fonctionne correctement (en supposant que le shell externe est également bash). L'exemple que vous avez posté montre qu'il fonctionne correctement - où les guillemets ont été modifiés se trouve dans la trace , car bash imprime des traces dans la syntaxe du shell, par exemple essayezbash -xc 'echo "hello world"'
Gilles 'SO- arrête d'être mauvais'
3
Excellente réponse. J'ai implémenté votre solution - je voudrais noter que vous pouvez importer le script lui-même à partir du script, à condition de l'imbriquer dans un conditionnel qui vérifie s'il sudo yourFunctionn'est pas trouvé (sinon vous obtenez une erreur de segmentation de la récursivité)
GrayedFox
5

Le «problème» est qu'il sudoefface l'environnement (à l'exception d'une poignée de variables autorisées) et définit certaines variables sur des valeurs de sécurité prédéfinies afin de se protéger contre les risques de sécurité. en d'autres termes, ce n'est pas réellement un problème. C'est une fonctionnalité.

Par exemple, si vous définissez PATH="/path/to/myevildirectory:$PATH"et sudoque vous ne définissez pas PATH sur une valeur prédéfinie, tout script qui ne spécifie pas le nom de chemin complet pour TOUTES les commandes qu'il exécute (c'est-à-dire la plupart des scripts) doit chercher /path/to/myevildirectoryavant tout autre répertoire. Mettez des commandes comme lsou grepou d'autres outils communs et vous pouvez facilement faire ce que vous voulez sur le système.

La façon la plus simple / la meilleure consiste à réécrire la fonction en tant que script et à l'enregistrer quelque part dans le chemin (ou spécifiez le chemin complet du script sur la sudoligne de commande - ce que vous devrez faire de toute façon, sauf s'il sudoest configuré pour vous permettre pour exécuter N'IMPORTE QUELLE commande en tant que root), et le rendre exécutable avecchmod +x /path/to/scriptname.sh

Réécriture une fonction shell en tant que script est aussi simple que l' enregistrement que les commandes à l' intérieur de la définition de la fonction dans un fichier (sans function ..., {et }lignes).

cas
la source
Cela ne répond en rien à la question. Il veut spécifiquement éviter de le mettre dans un script.
Will
1
sudo -EÉvite également de nettoyer l'environnement.
Will
Je comprends dans une certaine mesure pourquoi cela se produit. J'espérais qu'il y avait un moyen de contourner temporairement ce comportement. Quelque part ailleurs, une option -E a été mentionnée, bien que cela ne fonctionne pas dans ce cas. Malheureusement, même si j'apprécie l'explication de la façon d'en faire un script autonome, cela ne répond pas spécifiquement à la question, car je voulais un moyen d'éviter cela. Je n'ai aucun contrôle sur l'endroit où l'utilisateur final place le script et j'aimerais éviter à la fois les répertoires codés en dur et le chant et la danse d'essayer de déterminer avec précision d'où le script principal a été exécuté.
BryKKan
peu importe que ce soit ce que le PO a demandé ou non. Si ce qu'il veut ne fonctionne pas ou ne peut être mis au travail qu'en faisant quelque chose d'extrêmement précaire, il faut le leur dire et leur fournir une alternative - même si l'alternative est quelque chose qu'ils ont explicitement déclaré ne pas vouloir (parce que c'est parfois la seule ou la meilleure façon de le faire en toute sécurité). Il serait irresponsable de dire à quelqu'un comment se tirer une balle dans le pied sans l'avertir des conséquences probables de pointer une arme à feu à ses pieds et d'appuyer sur la détente.
cas
1
@cas C'est vrai. Cela ne peut pas être fait en toute sécurité est une réponse acceptable dans certaines circonstances. Voir ma dernière édition cependant. Je serais curieux de savoir si votre opinion sur les implications en matière de sécurité est la même étant donné cela.
BryKKan
3

J'ai écrit ma propre Sudofonction bash pour cela, cela fonctionne pour appeler des fonctions et des alias:

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
la source
2

Vous pouvez combiner des fonctions et des alias

Exemple:

function hello_fn() {
    echo "Hello!" 
}

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

sudo hellofonctionne alors

hbt
la source
1

Voici une variante de la réponse de Will . Il implique un catprocessus supplémentaire , mais offre le confort de heredoc. En résumé, cela se passe comme suit:

f () 
{
    echo ok;
}

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

Si vous voulez plus de matière à réflexion, essayez ceci:

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

La sortie est:

here's the menu
a difficult thing to pass

Ici, nous avons la menufonction correspondant à celle de la question, qui est "définie ailleurs dans le script principal". Si «ailleurs» signifie que sa définition a déjà été lue à ce stade lors de l' sudoexécution de la fonction exigeante , alors la situation est analogue. Mais il n'a peut-être pas encore été lu. Il se peut qu'une autre fonction déclenche sa définition. Dans ce cas, declare -f menudoit être remplacé par quelque chose de plus sophistiqué, ou le script entier corrigé d'une manière que la menufonction est déjà déclarée.

Tomasz
la source
Très intéressant. Je vais devoir l'essayer à un moment donné. Et oui, la menufonction aurait été déclarée avant ce point, comme on l' fappelle depuis un menu.
BryKKan
0

En supposant que votre script soit (a) autonome ou (b) peut source ses composants en fonction de son emplacement (plutôt que de se rappeler où se trouve votre répertoire personnel), vous pouvez faire quelque chose comme ceci:

  • utilisez le $0chemin d'accès du script, en utilisant celui de la sudocommande, et passez une option que le script vérifiera, pour appeler la mise à jour du mot de passe. Tant que vous comptez sur la recherche du script dans le chemin (plutôt que de simplement l'exécuter ./myscript), vous devriez obtenir un chemin d'accès absolu $0.
  • depuis qu'il sudoexécute le script, il a accès aux fonctions dont il a besoin dans le script.
  • en haut du script (au-delà des déclarations de fonction), le script vérifierait son uidet se rendrait compte qu'il était exécuté en tant qu'utilisateur root , et voyant qu'il a l'option définie pour lui dire de mettre à jour le mot de passe, allez faire cette.

Les scripts peuvent se reproduire pour diverses raisons: la modification des privilèges en fait partie.

Thomas Dickey
la source