Eh bien, vous pouvez toujours faire:
#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip
echo test
a=foo
set a b
SWITCH_TO_USER root
echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }
SWITCH_TO_USER rag
foo
set +x
SWITCH_TO_USER root again
echo "hi again from $(id -un)"
(ʘ‿ʘ)
Cela a d'abord commencé comme une blague car cela implémente ce qui est demandé, mais probablement pas exactement comme prévu, et n'est pas pratique. Mais comme cela a évolué vers quelque chose qui fonctionne dans une certaine mesure et implique quelques bons hacks, voici une petite explication:
Comme l'a dit Miroslav , si nous laissons de côté les capacités de style Linux (qui ne seraient pas vraiment utiles ici non plus), la seule façon pour un processus non privilégié de changer l'uid est d'exécuter un exécutable setuid.
Une fois que vous obtenez le privilège de superutilisateur (en exécutant un exécutable setuid dont le propriétaire est root par exemple), vous pouvez basculer l'ID utilisateur effectif entre votre ID utilisateur d'origine, 0 et tout autre ID, sauf si vous renoncez à votre ID utilisateur d'ensemble enregistré ( comme des choses comme sudoou sufont typiquement).
Par exemple:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Maintenant, j'ai une envcommande qui me permet d'exécuter n'importe quelle commande avec un identifiant utilisateur efficace et un identifiant utilisateur enregistré de 0 (mon identifiant réel étant toujours 1000):
$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
$>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4
perla des wrappers pour setuid/ seteuid(ceux-ci $>et les $<variables).
Zsh aussi:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Bien que ci-dessus, ces idcommandes soient appelées avec un identifiant utilisateur réel et un identifiant utilisateur enregistré de 0 (bien que si j'avais utilisé mon ./envau lieu de sudocela, cela n'aurait été que l'identifiant utilisateur enregistré, tandis que l'ID utilisateur réel serait resté 1000), ce qui signifie que s'il s'agissait de commandes non fiables, elles pourraient toujours faire des dégâts, vous devriez donc l'écrire à la place comme:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(c'est-à-dire tous les uids (ensemble effectif, réel et enregistré) juste pour l'exécution de ces commandes.
bashn'a aucun moyen de modifier les identifiants utilisateur. Donc, même si vous aviez un exécutable setuid avec lequel appeler votre bashscript, cela n'aiderait pas.
Avec bash, il vous reste à exécuter un exécutable setuid chaque fois que vous voulez changer d'uid.
L'idée dans le script ci-dessus est lors d'un appel à SWITCH_TO_USER, pour exécuter une nouvelle instance bash pour exécuter le reste du script.
SWITCH_TO_USER someuserest plus ou moins une fonction qui exécute à nouveau le script en tant qu'utilisateur différent (en utilisant sudo) mais en ignorant le début du script jusqu'à SWITCH_TO_USER someuser.
Là où cela devient difficile, c'est que nous voulons conserver l'état de la bash actuelle après avoir démarré la nouvelle bash en tant qu'utilisateur différent.
Décomposons-le:
{ shopt -s expand_aliases;
Nous aurons besoin d'alias. L'une des astuces de ce script consiste à ignorer la partie du script jusqu'au SWITCH_TO_USER someuser, avec quelque chose comme:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Ce formulaire est similaire à celui #if 0utilisé en C, c'est un moyen de commenter complètement du code.
:est un no-op qui retourne vrai. Donc en : || :, le second :n'est jamais exécuté. Cependant, il est analysé. Et << 'xxx'c'est une forme de document ici où (parce que xxxc'est cité), aucune expansion ou interprétation n'est faite.
On aurait pu faire:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Mais cela aurait signifié que le document ici aurait dû être écrit et transmis en tant que stdin à :. :||:évite cela.
Maintenant, où il devient hacky, c'est que nous utilisons le fait que les bashalias se développent très tôt dans son processus d'analyse. Avoir skipun alias pour la :||: << 'SWITCH_TO_USER someuther'partie de la construction de mise en commentaire .
Continuons:
SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}
Voici la définition de la fonction SWITCH_TO_USER . Nous verrons ci-dessous que SWITCH_TO_USER sera éventuellement un alias enroulé autour de cette fonction.
Cette fonction fait l'essentiel de la réexécution du script. En fin de compte, nous voyons qu'il se ré-exécute (dans le même processus à cause de exec) bashavec la _xvariable dans son environnement (nous l'utilisons envici car sudogénéralement assainit son environnement et ne permet pas de passer des vars env arbitraires à travers). Cela bashévalue le contenu de cette $_xvariable en tant que code bash et source le script lui-même.
_x est défini précédemment comme:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Toutes les declare, alias, shopt -p set +osortie constituent une décharge de l'état interne de la coquille. Autrement dit, ils vident la définition de toutes les variables, fonctions, alias et options sous forme de code shell prêt à être évalué. En plus de cela, nous ajoutons le réglage des paramètres de position ( $1, $2...) en fonction de la valeur du $_atableau (voir ci-dessous), et un peu de nettoyage afin que l'énorme $_xvariable ne reste pas dans l'environnement pour le reste du script.
Vous remarquerez que la première partie jusqu'à set +xest enveloppée dans un groupe de commandes dont stderr est redirigé vers /dev/null( {...} 2> /dev/null). C'est parce que, si à un moment donné du script set -x(ou set -o xtrace) est exécuté, nous ne voulons pas que ce préambule génère des traces car nous voulons le rendre le moins intrusif possible. Nous exécutons donc un set +x(après avoir pris soin de vider l'option (y compris xtrace) les paramètres au préalable) où les traces sont envoyées à / dev / null.
Le eval "$_X"stderr est également redirigé vers / dev / null pour des raisons similaires, mais également pour éviter les erreurs d'écriture lors d'une tentative de lecture de variables spéciales en lecture seule.
Continuons avec le script:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
C'est notre astuce décrite ci-dessus. Lors de l'appel initial du script, il sera annulé (voir ci-dessous).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Maintenant, l'encapsuleur d'alias autour de SWITCH_TO_USER. La raison principale est de pouvoir passer les paramètres positionnels ( $1, $2...) au nouveau bashqui interprétera le reste du script. Nous ne pouvions pas le faire dans la SWITCH_TO_USER fonction car à l'intérieur "$@"de la fonction, se trouvent les arguments des fonctions, pas ceux des scripts. La redirection stderr vers / dev / null consiste à nouveau à masquer les xtraces, et evalà contourner un bogue dans bash. Ensuite, nous appelons la SWITCH_TO_USER fonction .
${_u+:} alias skip=:
Cette partie annule l' skipalias (le remplace par la :commande no-op) sauf si la $_uvariable est définie.
skip
Voilà notre skipalias. Lors de la première invocation, ce sera juste :(le no-op). Sur les invocations de re-subsequence, ce sera quelque chose comme: :||: << 'SWITCH_TO_USER root'.
echo test
a=foo
set a b
SWITCH_TO_USER root
Donc, ici, à titre d'exemple, à ce stade, nous réinvoquons le script en tant rootqu'utilisateur, et le script restaurera l'état enregistré, et sautera jusqu'à cette SWITCH_TO_USER rootligne et continuera.
Cela signifie qu'il doit être écrit exactement comme stat, avec SWITCH_TO_USERau début de la ligne et avec exactement un espace entre les arguments.
La plupart des états, stdin, stdout et stderr seront conservés, mais pas les autres descripteurs de fichiers car sudogénéralement ils les ferment sauf s'ils sont explicitement configurés pour ne pas le faire. Ainsi, par exemple:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
ne fonctionnera généralement pas.
Notez également que si vous le faites:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Cela ne fonctionne que si vous avez le droit à sudoas aliceet alicele droit à sudoas bobet bobas root.
Donc, en pratique, ce n'est pas vraiment utile. Utiliser à la suplace de sudo(ou une sudoconfiguration où sudoauthentifie l'utilisateur cible au lieu de l'appelant) pourrait être un peu plus logique, mais cela signifierait toujours que vous auriez besoin de connaître les mots de passe de tous ces gars-là.