Ejecutar una función de script Bash con Sudo

22

Tengo un script que hace varias cosas diferentes, la mayoría de las cuales no requieren privilegios especiales. Sin embargo, una sección específica, que he contenido dentro de una función, necesita privilegios de root.

No deseo requerir que todo el script se ejecute como root, y quiero poder llamar a esta función, con privilegios de root, desde el script. Solicitar una contraseña si es necesario no es un problema, ya que de todos modos es principalmente interactivo. Sin embargo, cuando trato de usar sudo functionx, obtengo:

sudo: functionx: command not found

Como esperaba, exportno hizo la diferencia. Me gustaría poder ejecutar la función directamente en el script en lugar de dividirla y ejecutarla como un script separado por varias razones.

¿Hay alguna forma de hacer que mi función sea "visible" para sudo sin extraerla, encontrar el directorio apropiado y luego ejecutarlo como un script independiente?

La función se trata de una página en sí misma y contiene múltiples cadenas, algunas con comillas dobles y otras con comillas simples. También depende de una función de menú definida en otra parte del script principal.

Solo esperaría que alguien con sudo CUALQUIERA pueda ejecutar la función, ya que una de las cosas que hace es cambiar las contraseñas.

BryKKan
fuente
El hecho de que haya varias funciones involucradas lo hace aún más complicado y propenso a fallar. Ahora tiene que encontrar todas esas dependencias (y todas sus dependencias también, si las hay ... a muchos niveles) incluyendo cualquier otra función que la función del menú pueda llamar y declaretambién a ellas.
cas
De acuerdo, y es posible que tenga que morder la bala y dividirla (y hacer todo lo posible para determinar con precisión la ruta desde la que se ejecutó, además de esperar que el usuario final mantenga los archivos juntos) si no hay mejores alternativas.
BryKKan

Respuestas:

19

Admito que no hay una forma simple e intuitiva de hacer esto, y esto es un poco hackey. Pero, puedes hacerlo así:

function hello()
{
    echo "Hello!"
}

# Test that it works.
hello

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

O más simplemente:

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

Esto funciona para mi:

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

Básicamente, declare -fdevolverá el contenido de la función, que luego pasará a en bash -clínea.

Si desea exportar todas las funciones desde la instancia externa de bash, cambie FUNC=$(declare -f hello)a FUNC=$(declare -f).

Editar

Para abordar los comentarios sobre las citas, vea este ejemplo:

$ 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.
Será
fuente
1
Esto solo funciona por accidente, porque echo "Hello!"es efectivamente el mismo que echo Hello!(es decir, las comillas dobles no hacen ninguna diferencia para este comando de eco en particular). En muchas otras circunstancias, las comillas dobles en la función pueden romper el bash -ccomando.
cas
1
Esto responde a la pregunta original, así que si no obtengo una solución mejor, la aceptaré. Sin embargo, rompe mi función particular (ver mi edición) ya que depende de funciones definidas en otra parte del script.
BryKKan
1
Hice algunas pruebas esta tarde (usando bash -xcen lugar de sólo bash -c) y parece que bashes lo suficientemente inteligente como para las cosas volver a citar en esta situación, incluso hasta el punto de sustituir las comillas dobles con comillas simples y cambiando 'a '\''si es necesario. Estoy seguro de que habrá algunos casos que no puede manejar, pero definitivamente funciona para casos al menos simples y moderadamente complejos, por ejemplo, intentefunction hello() { filename="this is a 'filename' with single quotes and spaces" ; echo "$filename" ; } ; FUNC=$(declare -f hello) ; bash -xc "$FUNC ; hello"
cas
44
@cas declare -fimprime la definición de la función de manera que bash pueda volver a analizarla, por lo bash -c "$(declare -f)" que funciona correctamente (suponiendo que la capa externa también sea bash). El ejemplo que publicó muestra que funciona correctamente: el lugar donde se cambiaron las comillas está en la traza , porque bash imprime trazas en la sintaxis de shell, por ejemplo, intentebash -xc 'echo "hello world"'
Gilles 'SO- deje de ser malvado'
3
Excelente respuesta Implementé su solución: me gustaría señalar que puede importar el script en sí desde el script, siempre que lo anide dentro de un condicional que verifica si no sudo yourFunctionse encuentra (de lo contrario, obtiene un error de segmentación de la recursión)
GrayedFox
5

El "problema" es que sudoborra el entorno (a excepción de un puñado de variables permitidas) y establece algunas variables en valores seguros predefinidos para proteger contra riesgos de seguridad. en otras palabras, esto no es realmente un problema. Es una característica

Por ejemplo, si configuró PATH="/path/to/myevildirectory:$PATH"y sudono configuró PATH en un valor predefinido, cualquier script que no especifique el nombre de ruta completo para TODOS los comandos que ejecuta (es decir, la mayoría de los scripts) se vería /path/to/myevildirectoryantes que cualquier otro directorio. Ponga comandos como lsu grepotras herramientas comunes allí y podrá hacer fácilmente lo que quiera en el sistema.

La forma más fácil / mejor es volver a escribir la función como un script y guardarla en algún lugar de la ruta (o especificar la ruta completa al script en la sudolínea de comandos, lo que deberá hacer de todos modos a menos que sudoesté configurado para permitirle ejecutar CUALQUIER comando como root) y hacerlo ejecutable conchmod +x /path/to/scriptname.sh

La reescritura de una función de shell como un guión es tan simple como el ahorro de los comandos dentro de la definición de función en un archivo (sin el function ..., {y }líneas).

cas
fuente
Esto no responde a la pregunta de ninguna manera. Él específicamente quiere evitar ponerlo en un guión.
Será el
1
También sudo -Eevita limpiar el medio ambiente.
Será el
Entiendo hasta cierto punto por qué está sucediendo. Esperaba que hubiera algún medio para anular temporalmente este comportamiento. En otro lugar se mencionó una opción -E, aunque en este caso no funcionó. Desafortunadamente, si bien aprecio la explicación de cómo convertirlo en un script independiente, eso específicamente no responde la pregunta, porque quería un medio para evitar eso. No tengo control sobre dónde el usuario final coloca el guión y me gustaría evitar tanto los directorios codificados como la canción y el baile de tratar de determinar con precisión de dónde se ejecutó el guión principal.
BryKKan
no importa si eso fue lo que solicitó el OP o no. Si lo que quiere no funciona o solo se puede hacer que funcione haciendo algo extremadamente inseguro, entonces se les debe decir eso y proporcionarles una alternativa, incluso si la alternativa es algo que declararon explícitamente que no quieren (porque a veces esa es la única o la mejor manera de hacerlo de forma segura). Sería irresponsable decirle a alguien cómo dispararse en el pie sin advertirle sobre las posibles consecuencias de apuntar con un arma a sus pies y apretar el gatillo.
cas
1
@cas Eso es cierto. No se puede hacer de forma segura es una respuesta aceptable en algunas circunstancias. Ver mi última edición sin embargo. Me gustaría saber si su opinión sobre las implicaciones de seguridad es la misma dado eso.
BryKKan
3

He escrito mi propia Sudofunción bash para hacer eso, funciona para llamar a funciones y 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
fuente
2

Puedes combinar funciones y alias

Ejemplo:

function hello_fn() {
    echo "Hello!" 
}

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

entonces sudo hellofunciona

hbt
fuente
1

Aquí hay una variación en la respuesta de Will . Implica un catproceso adicional , pero ofrece la comodidad de heredoc. En pocas palabras, es así:

f () 
{
    echo ok;
}

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

Si quieres más comida para pensar, prueba esto:

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

El resultado es:

here's the menu
a difficult thing to pass

Aquí tenemos la menufunción correspondiente con la de la pregunta, que está "definida en otra parte del script principal". Si "en otro lugar" significa que su definición ya se ha leído en esta etapa cuando sudose ejecuta la función exigente , entonces la situación es análoga. Pero puede que aún no se haya leído. Puede haber otra función que aún activará su definición. En este caso declare -f menutiene que ser reemplazado por algo más sofisticado, o todo el script corregido de una manera que la menufunción ya esté declarada.

Tomász
fuente
Muy interesante. Tendré que probarlo en algún momento. Y sí, la menufunción se habría declarado antes de este punto, como fse invoca desde un menú.
BryKKan
0

Suponiendo que su script sea (a) autocontenido o (b) pueda obtener sus componentes en función de su ubicación (en lugar de recordar dónde está su directorio de inicio), puede hacer algo como esto:

  • use el $0nombre de ruta para el script, use eso en el sudocomando, y pase una opción que el script verificará, para llamar a la actualización de contraseña. Siempre y cuando confíes en encontrar el script en la ruta (en lugar de solo ejecutarlo ./myscript), deberías obtener un nombre de ruta absoluto $0.
  • dado que sudoejecuta el script, tiene acceso a las funciones que necesita en el script.
  • en la parte superior de la secuencia de comandos (más bien, más allá de las declaraciones de funciones), la secuencia de comandos verificará su uidy se dará cuenta de que se ejecutó como usuario raíz , y al ver que tiene la opción establecida para indicarle que actualice la contraseña, vaya y haga ese.

Los scripts pueden repetirse en sí mismos por varias razones: el cambio de privilegios es uno de esos.

Thomas Dickey
fuente