Utiliser le fichier de configuration pour mon script shell

Jai besoin de créer un fichier de configuration pour mon propre script:
Voici un exemple:

script:

#!/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 faire ceci ou y a-t-il « dautres moyens?

Commentaires

  • Les variables doivent être en haut. I ‘ m surpris que cela fonctionne. Quoi quil en soit, pourquoi avez-vous besoin dun fichier de configuration? Envisagez-vous dutiliser ces variables ailleurs?
  • Faheem, jai besoin des variables car mon script a de nombreuses options: utiliser un Le fichier de configuration illustrera le script. Merci
  • IMHO cest très bien. Je ferais de cette façon.
  • abcde le fait aussi de cette façon et cest un programme assez volumineux (pour un script shell). Vous pouvez le consulter ici .

La réponse

source nest pas sécurisée car elle exécutera du code arbitraire. Cela peut ne pas vous inquiéter, mais si les autorisations de fichier sont incorrectes, il peut être possible pour un attaquant disposant dun accès au système de fichiers dexécuter du code en tant quutilisateur privilégié en injectant du code dans un fichier de configuration chargé par un script autrement sécurisé tel quun init script.

Jusquà présent, la meilleure solution que jai pu identifier est la solution maladroite de réinventer la roue:

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 modifierait lutilisateur « s $PROMPT_COMMAND. À la place, faites ceci:

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.

Commentaires

  • Pour info, il sagit dune solution Bash version 4.0 qui est malheureusement sujette à problèmes de licence insensés imposés par Apple et nest pas disponible par défaut sur les Mac
  • @Sukima Bon point. Jai ‘ ajouté une version compatible avec Bash 3. Sa faiblesse est quil ne gérera pas correctement * dans les entrées, mais alors Quest-ce qui dans Bash gère bien ce caractère?
  • Le premier script échoue si le mot de passe contient une barre oblique inverse.
  • @Kusalananda Et si la barre oblique inverse est échappée? my\\password
  • Cette version prend plusieurs secondes pour traiter mon fichier de configuration, jai trouvé la solution de gw0 beaucoup plus rapide.

Réponse

Analyser le fichier de configuration, ne pas lexécuter.

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 »), cest ce que je fais pour obtenir le nom dutilisateur (plus ou moins, je « lai 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.

Jutilise XML car dautres parties de lapplication traitent également des données encodées dans des fichiers XML, donc cétait plus simple.

Si vous préférez JSON, il « s jq qui est un analyseur JSON shell facile à utiliser.

Mon fichier de configuration ressemblerait à quelque chose comme ça dans JS ON:

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

Et puis jobtiendrais le nom dutilisateur dans le script:

username="$( jq -r ".username" "$config_file" )" 

Commentaires

  • Lexécution du script présente un certain nombre davantages et dinconvénients. Les principaux inconvénients sont la sécurité, si quelquun peut modifier le fichier de configuration, il peut exécuter du code, et il est plus difficile de le rendre idiot. Les avantages sont la rapidité, sur un simple test, il est plus de 10000 fois plus rapide de trouver un fichier de configuration que dexécuter pq, et la flexibilité, quiconque aime le patching de singe python appréciera cela.
  • @icarus les fichiers de configuration que vous rencontrez habituellement et à quelle fréquence devez-vous les analyser en une seule session? Notez également que plusieurs valeurs peuvent être extraites de XML ou JSON en une seule fois.
  • En général, seules quelques valeurs (1 à 3). Si vous utilisez eval pour définir plusieurs valeurs, vous exécutez des parties sélectionnées du fichier de configuration :-).
  • @icarus Je pensais aux tableaux … Pas besoin de eval quoi que ce soit. La performance de lutilisation dun format standard avec un analyseur existant (même si ‘ est un utilitaire externe) est négligeable par rapport à la robustesse, la quantité de code, la facilité dutilisation et maintenabilité.
  • +1 pour  » analyser le fichier de configuration, ne ‘ lexécuter  »

Réponse

Voici une version propre et portable compatible avec Bash 3 et up, à la fois sur Mac et Linux.

Il spécifie toutes les valeurs par défaut dans un fichier séparé, pour éviter le besoin dune énorme fonction de configuration « par défaut » dupliquée dans tous vos scripts shell. Et il vous permet de choisir entre la lecture avec ou sans solutions de secours par défaut:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (il sagit dune bibliothèque, donc il ny 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 voulez 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 placées entre guillemets. En enveloppant chaque config_get entre guillemets, nous nous assurons que le texte de la valeur de la variable ne sera jamais interprété comme des indicateurs. Et cela garantit que nous préservons correctement les espaces blancs, tels que plusieurs espaces dans une ligne dans la valeur de configuration.
  • Et quest-ce que cette ligne printf? « Cest quelque chose dont vous devez être conscient: echo est une mauvaise commande pour imprimer du texte sur lequel vous navez 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, parce que echo pensera que cest « un indicateur. Mais printf na pas ce problème. Le printf -- dit « imprimez ceci et ninterprétez rien comme des indicateurs », et le "%s\n" dit « formatez la sortie sous forme de chaîne avec une nouvelle ligne à la fin, et enfin le paramètre final est la valeur pour printf à formater.
  • Si vous nallez pas faire écho aux valeurs à lécran, alors vous les attribuez simplement normalement, comme myvar="$(config_get myvar)";. Si vous « allez les imprimer à lécran, je suggère dutiliser printf pour être totalement sûr contre toutes les chaînes incompatibles avec lécho qui peuvent être dans la configuration utilisateur. Mais echo convient si la variable fournie par lutilisateur nest pas » t le premier caractère de la chaîne à laquelle vous faites écho, car cest « la seule situation où » flags « pourrait être interprété, donc quelque chose comme echo "foo: $(config_get myvar)"; est sûr, puisque le » toto « ne commence » pas par un tiret et indique donc à echo que le reste de la chaîne nest pas non plus un indicateur pour lui. 🙂

Commentaires

  • @ user2993656 Merci davoir remarqué que mon code dorigine contenait toujours mon nom de fichier de configuration privé (environment.cfg) au lieu du nom correct. Quant au  » echo -n  » que vous avez fait, cela dépend du shell utilisé. Sous Mac / Linux Bash,  » echo -n  » signifie  » écho sans retour à la ligne « , ce que jai fait pour éviter les retours à la ligne. Mais cela semble fonctionner exactement de la même manière sans lui, alors merci pour les modifications!
  • En fait, je viens de le réécrire pour utiliser printf au lieu de echo, ce qui garantit que nous ‘ Je vous débarrasserai du risque de mauvaise interprétation des échos  » indicateurs  » dans les valeurs de configuration.
  • Jaime vraiment cette version. Jai laissé tomber les config.cfg.defaults au lieu de les définir au moment de lappel $(config_get var_name "default_value"). tritarget.org/static/…
  • De même – cest génial.

Réponse

Le moyen le plus courant, le plus efficace et le plus correct est dutiliser source, ou . comme forme abrégée. Par exemple:

source /home/myuser/test/config 

ou

. /home/myuser/test/config 

Cependant, il faut prendre en compte le les problèmes de sécurité que lutilisation dun fichier de configuration externe supplémentaire peut soulever, étant donné que du code supplémentaire peut être inséré. Pour plus dinformations, notamment sur la manière de détecter et de résoudre ce problème, je vous recommande de consulter la section « Sécuriser » de http://wiki.bash-hackers.org/howto/conffile#secure_it

Commentaires

  • Javais de grands espoirs pour cet article (également apparu dans mes résultats de recherche), mais lauteur ‘ dessayer dutiliser des regex pour filtrer le code malveillant est un exercice futile.
  • La procédure avec un point nécessite un chemin absolu?Avec le relatif, il ne ‘ t fonctionne

Réponse

Jutilise 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 } 

Doit prendre en charge toutes les combinaisons de caractères, sauf que les clés ne peuvent « t avoir = en eux, puisque cest 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 

De plus, cest totalement sûr car il nutilise pas source ou eval.

Commentaires

  • Isn ‘ t ceci est supposé créer dabord le fichier de configuration sil nexiste pas ‘? Il nexiste ‘ t, mais touch -a "${path}" sassurera bien sûr quil existe, même sans mettre à jour frivolement son mtime.

Réponse

Cest succinct et sécurisé:

# 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 nobtenez que 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, cest-à-dire en utilisant bsd env.

Commentaires

  • Voyez simplement comment les fichiers evil1 et evil2 sont créés si vous mettez ceci dans comm on.vars «  «  touch evil1 foo = omg boo = $ (touch evil2) «  « 
  • @pihentagy Pour moi, ce qui suit ne produit aucun fichier touché env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Exécution sur mac.
  • en effet, mais ne peut pas accéder à foo. Jai ‘ essayé env -i ... myscript.sh et à lintérieur de ce script foo nest pas défini. Cependant, si vous supprimez  » garbage « , cela fonctionnera. Alors merci pour lexplication. : +1:

Réponse

La plupart des utilisateurs, bien que peu de conteneurs, possèdent déjà le git binaire. Pourquoi ne pas donc utiliser git config pour la gestion de la configuration de lapplication en utilisant un fichier de configuration dédié non conflictuel comme dans les exemples ci-dessous?

# Set $ git config -f ~/.myapp core.mykey myval # Get $ git config -f ~/.myapp core.mykey myval # Get invalid $ git config -f ~/.myapp core.mykey $ echo $? 1 # List git config -f ~/.myapp -l core.mykey=myval # View $ cat ~/.myapp [core] mykey = myval 

Pour des commandes supplémentaires, voir son man page. Il est préférable de sassurer que le fichier de configuration existe:

touch -a ~/.myapp 

Réponse

Celui-ci semble sûr et court. Nhésitez pas à le craquer sans pitié. Je « voudrais connaître un meilleur moyen.

TL; DR;

while read LINE; do declare "$LINE"; done < evil.conf 

Je » m utilise bash 4.3.48.

Il est également compatible avec bash --posix. Voir le bas pour le test.

Mais sh ne le prend pas en charge à cause de declare.


Test de base pour ceux qui veulent une preuve

Créer un fichier evil.conf

 echo > evil.conf " A=1 B=2 C=$(echo hello) # Could produce side-effect D=`touch evil` C=$((1+2)) E=$(ping 8.8.8.8 -n 3) echo hello # Could produce visible side-effect touch evil2 ping 8.8.8.8 -n 3 F=ok"  

Chargez la configuration avec lextrait de code

 while read LINE; do declare "$LINE"; done < evil.conf  

Sortie (voir le désinfectant en action)

bash: declare: `": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce side-effect": not a valid identifier bash: declare: `echo hello": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce visible side-effect": not a valid identifier bash: declare: `touch evil2": not a valid identifier bash: declare: `ping 8.8.8.8 -n 3": not a valid identifier 

Vérifions les valeurs maintenant

 for V in A B C D E F; do declare -p $V; done  
declare -- A="1" declare -- B="2" declare -- C="\$((1+2))" declare -- D="\`touch evil\`" declare -- E="\$(ping 8.8.8.8 -n 3)" declare -- F="ok" 

Vérifier les effets secondaires (pas deffets secondaires):

 ls evil evil2  
ls: cannot access "evil": No such file or directory ls: cannot access "evil2": No such file or directory 

Annexe. Test bash --posix

bash -c "while read LINE; do declare "$LINE"; done < evil.conf; for V in A B C D E F; do declare -p $V; done" --posix 

Réponse

Pour mon scénario, source ou . w aussi bien, mais je voulais prendre en charge les variables denvironnement locales (cest-à-dire FOO=bar myscript.sh) en prenant le pas sur les variables configurées. Je voulais aussi que le fichier de configuration soit modifiable par lutilisateur et confortable pour quelquun qui a lhabitude de trouver des fichiers de configuration, et quil soit aussi petit / simple que possible, pour ne pas détourner lattention de lorientation principale de mon tout petit script.

Voici ce que jai 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 de variables (sans être très flexible sur les espaces) et réécrit ces lignes afin que le La valeur est convertie en valeur par défaut pour cette variable, et la variable nest pas modifiée si elle est trouvée, comme la variable XDG_CONFIG_HOME ci-dessus. Il fournit 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  » t définitions, etc., ne sautent pas sur les commentaires de fin de ligne – mais cela me suffit pour le moment.

Réponse

Vous pouvez le faire:

#!/bin/bash name="mohsen" age=35 cat > /home/myuser/test/config << EOF Name=$name Age=$age EOF 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *