web-dev-qa-db-fra.com

Utiliser le fichier de configuration pour mon script Shell

J'ai besoin de créer un fichier de configuration pour mon propre script: voici un exemple:

scénario:

#!/bin/bash
source /home/myuser/test/config
echo "Name=$nam" >&2
echo "Surname=$sur" >&2

Contenu de /home/myuser/test/config:

nam="Mark"
sur="Brown"

ça marche!

Ma question: est-ce la bonne façon de procéder ou y a-t-il d'autres façons?

27
Pol Hallen

source n'est pas sécurisé car il exécutera du code arbitraire. Cela peut ne pas vous préoccuper, mais si les autorisations de fichiers sont incorrectes, il peut être possible pour un attaquant disposant d'un accès au système de fichiers d'exécuter du code en tant qu'utilisateur privilégié en injectant du code dans un fichier de configuration chargé par un script sécurisé tel qu'un script d'initialisation.

Jusqu'à présent, la meilleure solution que j'ai pu identifier est la solution maladroite de réinvention du volant:

myscript.conf

password=bar
echo rm -rf /
Prompt_COMMAND='echo "Sending your last command $(history 1) to my email"'
hostname=localhost; echo rm -rf /

En utilisant source, cela exécuterait echo rm -rf / deux fois, ainsi que modifier l'utilisateur en cours d'exécution $Prompt_COMMAND. Au lieu de cela, procédez comme suit:

myscript.sh (Bash 4)

#!/bin/bash
typeset -A config # init array
config=( # set default values in config array
    [username]="root"
    [password]=""
    [hostname]="localhost"
)

while read line
do
    if echo $line | grep -F = &>/dev/null
    then
        varname=$(echo "$line" | cut -d '=' -f 1)
        config[$varname]=$(echo "$line" | cut -d '=' -f 2-)
    fi
done < myscript.conf

echo ${config[username]} # should be loaded from defaults
echo ${config[password]} # should be loaded from config file
echo ${config[hostname]} # includes the "injected" code, but it's fine here
echo ${config[Prompt_COMMAND]} # also respects variables that you may not have
               # been looking for, but they're sandboxed inside the $config array

myscript.sh (compatible Mac/Bash 3)

#!/bin/bash
config() {
    val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d '=' -f 2-)

    if [[ $val == __DEFAULT__ ]]
    then
        case $1 in
            username)
                echo -n "root"
                ;;
            password)
                echo -n ""
                ;;
            hostname)
                echo -n "localhost"
                ;;
        esac
    else
        echo -n $val
    fi
}

echo $(config username) # should be loaded from defaults
echo $(config password) # should be loaded from config file
echo $(config hostname) # includes the "injected" code, but it's fine here
echo $(config Prompt_COMMAND) # also respects variables that you may not have
               # been looking for, but they're sandboxed inside the $config array

Veuillez répondre si vous trouvez un exploit de sécurité dans mon code.

19
Mikkel

Voici une version propre et portable compatible avec Bash 3 et plus, sur Mac et Linux.

Il spécifie toutes les valeurs par défaut dans un fichier séparé, pour éviter d'avoir à utiliser une énorme fonction de configuration "par défaut" encombrée et dupliquée dans tous vos scripts Shell. Et il vous permet de choisir entre la lecture avec ou sans repli par défaut:

config.cfg:

myvar=Hello World

config.cfg.defaults:

myvar=Default Value
othervar=Another Variable

config.shlib (c'est une bibliothèque, donc il n'y a pas de ligne Shebang):

config_read_file() {
    (grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}

config_get() {
    val="$(config_read_file config.cfg "${1}")";
    if [ "${val}" = "__UNDEFINED__" ]; then
        val="$(config_read_file config.cfg.defaults "${1}")";
    fi
    printf -- "%s" "${val}";
}

test.sh (ou tout script où vous souhaitez lire les valeurs de configuration):

#!/usr/bin/env bash
source config.shlib; # load the config library functions
echo "$(config_get myvar)"; # will be found in user-cfg
printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing!
myvar="$(config_get myvar)"; # how to just read a value without echoing
echo "$(config_get othervar)"; # will fall back to defaults
echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn't set anywhere

Explication du script de test:

  • Notez que toutes les utilisations de config_get dans test.sh sont entourées de guillemets doubles. En enveloppant chaque config_get entre guillemets doubles, nous nous assurons que le texte dans la valeur de la variable ne sera jamais interprété à tort comme indicateurs. Et cela garantit que nous préservons correctement les espaces blancs, tels que plusieurs espaces consécutifs dans la valeur de configuration.
  • Et quelle est cette ligne printf? Eh bien, c'est quelque chose que vous devez savoir: echo est une mauvaise commande pour imprimer du texte sur lequel vous n'avez aucun contrôle. Même si vous utilisez des guillemets doubles, il interprétera les indicateurs. Essayez de définir myvar (dans config.cfg) Sur -e Et vous verrez une ligne vide, car echo pensera que c'est un drapeau. Mais printf n'a pas ce problème. Le printf -- Dit "imprimez ceci, et n'interprétez rien comme des drapeaux", et le "%s\n" Dit "formatez la sortie comme une chaîne avec une nouvelle ligne de fin, et enfin le paramètre final est le valeur pour printf à formater.
  • Si vous n'allez pas faire écho de valeurs à l'écran, vous devez simplement les affecter normalement, comme myvar="$(config_get myvar)";. Si vous allez les imprimer à l'écran, je suggère d'utiliser printf pour être totalement sûr contre toutes les chaînes incompatibles avec l'écho qui peuvent être dans la configuration utilisateur. Mais l'écho est bien si la variable fournie par l'utilisateur n'est pas le premier caractère de la chaîne que vous faites écho, car c'est la seule situation où "flags" pourrait être interprété, donc quelque chose comme echo "foo: $(config_get myvar)"; est sûr, car "foo" ne commence pas par un tiret et indique donc à echo que le reste de la chaîne n'est pas non plus un indicateur pour lui. :-)
12
gw0

Analysez le fichier de configuration, ne l'exécutez pas.

J'écris actuellement une application au travail qui utilise une configuration XML extrêmement simple:

<config>
    <username>username-or-email</username>
    <password>the-password</password>
</config>

Dans le script Shell ("l'application"), voici ce que je fais pour obtenir le nom d'utilisateur (plus ou moins, je l'ai mis dans une fonction Shell):

username="$( xml sel -t -v '/config/username' "$config_file" )"

La commande xml est XMLStarlet , qui est disponible pour la plupart des Unices.

J'utilise XML car d'autres parties de l'application traitent également des données encodées dans des fichiers XML, donc c'était plus simple.

Si vous préférez JSON, il y a jq qui est un analyseur JSON Shell facile à utiliser.

Mon fichier de configuration ressemblerait à ceci en JSON:

{                                 
  "username": "username-or-email",
  "password": "the-password"      
}                

Et puis j'obtiendrais le nom d'utilisateur dans le script:

username="$( jq -r '.username' "$config_file" )"
9
Kusalananda

La manière la plus courante, efficace et correcte consiste à utiliser source ou . comme forme abrégée. Par exemple:

source /home/myuser/test/config

ou

. /home/myuser/test/config

Cependant, il convient de prendre en compte les problèmes de sécurité que l'utilisation d'un fichier de configuration externe supplémentaire peut soulever, étant donné que du code supplémentaire peut être inséré. Pour plus d'informations, y compris sur la façon de détecter et de résoudre ce problème, je recommanderais de jeter un œil à la section 'Secure it' de http://wiki.bash-hackers.org/howto/conffile#secure_it

3
NFarrington

J'utilise ceci dans mes scripts:

sed_escape() {
  sed -e 's/[]\/$*.^[]/\\&/g'
}

cfg_write() { # path, key, value
  cfg_delete "$1" "$2"
  echo "$2=$3" >> "$1"
}

cfg_read() { # path, key -> value
  test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1
}

cfg_delete() { # path, key
  test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1"
}

cfg_haskey() { # path, key
  test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null
}

Devrait prendre en charge toutes les combinaisons de caractères, sauf que les clés ne peuvent pas avoir = en eux, puisque c'est le séparateur. Tout le reste fonctionne.

% cfg_write test.conf mykey myvalue
% cfg_read test.conf mykey
myvalue
% cfg_delete test.conf mykey
% cfg_haskey test.conf mykey || echo "It's not here anymore"
It's not here anymore

En outre, cela est totalement sûr car il n'utilise aucun type de source/eval

2
2xsaiko

C'est succinct et sûr:

# Read common vars from common.vars
# the incantation here ensures (by env) that only key=value pairs are present
# then declare-ing the result puts those vars in our environment 
declare $(env -i `cat common.vars`)

Le -i garantit que vous obtenez uniquement les variables de common.vars

Mise à jour: une illustration de la sécurité est que

env -i 'touch evil1 foo=omg boo=$(touch evil2)'

Ne produira aucun fichier touché. Testé sur mac avec bash, c'est-à-dire en utilisant bsd env.

2
Marcin

Pour mon scénario, source ou . allait bien, mais je voulais prendre en charge les variables d'environnement locales (c'est-à-dire FOO=bar myscript.sh) ayant priorité sur les variables configurées. Je voulais également que le fichier de configuration soit modifiable par l'utilisateur et confortable pour quelqu'un habitué aux fichiers de configuration, et qu'il soit aussi petit/simple que possible, afin de ne pas distraire de l'objectif principal de mon tout petit script.

Voici ce que j'ai trouvé:

CONF=${XDG_CONFIG_HOME:-~/config}/myscript.sh
if [ ! -f $CONF ]; then
    cat > $CONF << CONF
VAR1="default value"
CONF
fi
. <(sed 's/^\([^=]\+\) *= *\(.*\)$/\1=${\1:-\2}/' < $CONF)

Essentiellement - il vérifie les définitions des variables (sans être très flexible sur les espaces blancs) et réécrit ces lignes afin que la valeur soit convertie en valeur par défaut pour cette variable, et que la variable ne soit pas modifiée si elle est trouvée, comme le XDG_CONFIG_HOME variable ci-dessus. Il source cette version modifiée du fichier de configuration et continue.

Les travaux futurs pourraient rendre le script sed plus robuste, filtrer les lignes qui semblent étranges ou qui ne sont pas des définitions, etc., ne pas casser les commentaires de fin de ligne - mais cela me suffit pour le moment.

0
Iiridayn