Ich habe Schwierigkeiten, einen Hinweis oder eine Lösung zum Lesen von OpenSSL-Konfigurationswerten in einem Shell-Skript zu finden. Lassen Sie mich Ihnen ein paar weitere Details geben.
ich habe einopenssl.confDatei mit folgendem vereinfachten Inhalt:
[ca_one]
dir = /home/auhorities/ca_one
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ca_two]
dir = /home/auhorities/ca_two
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
Ich schreibe ein Bash-Skript, das im Verzeichnis dir/certs nach jeder Zertifizierungsstelle sucht, d. h. ich muss Werte abrufen/home/behörden/ca_one/certsUnd/home/behörden/ca_two/certsaus der Datei. Vorerst bin ich bei der folgenden Lösung gelandet:
#!/usr/bin/env bash
for certs_dir in $(grep -E ^dir.+ openssl.conf | tr -d ' ' | cut -d '=' -f 2); do
echo "$certs_dir/certs"
done
Ich befürchte jedoch, dass dies nicht ideal ist, wenn die Skriptfunktionalität in Zukunft aktualisiert wird. Ich suche nach einer Lösung, die es mir oder meinen Kollegen in Zukunft ermöglicht, bequemer durch die Einträge der Zertifizierungsstelle zu navigieren. Ich habe beispielsweise openssl gefunden.KonfigurationHandbuch(https://www.openssl.org/docs/manmaster/man5/config.html), die besagt, dass dieKonfigurationBibliothek "kann zum Lesen von Konfigurationsdateien verwendet werden". Soweit ich weiß, wird diese Bibliothek intern von OpenSSL-Tools wieca, Anforderungoder etwas anderes. Ist es dann möglich, die Einträge aus der Konfigurationsdatei mit einigen der OpenSSL-Dienstprogramme zu lesen? Ich übersehe wahrscheinlich etwas, aber ich kann einfach nicht herausfinden, ob es möglich ist oder nicht. Wenn dies keine Option ist, wie würden Sie mit einer solchen Situation umgehen?
Vielen Dank im Voraus.
Antwort1
Es gibt kein offizielles Befehlszeilentool zum Parsen oder Validieren von openssl.cnf. Ich habe bash
jedoch eine kompatible Funktion erstellt.man configist sicherlich etwas, das Sie lesen und wieder lesen müssen.
WICHTIG: Wenn Ihre Konfiguration kaputt ist, funktioniert das nicht. Beheben Sie das Problem!
Das Beispiel openssl.cnf
muss zu Testzwecken etwas ausführlicher sein:
#set this! it forces ${var}/$(var); treats $var as a string literal
.pragma = dollarid:true
#with a huge config you are eventually going to want to section it out
.include my_other_conf.cnf
#safe variable mapping, always use to avoid config errors
DS = / #set a default variable value (DS = \ in Windows)
DS = ${ENV::DS} #value above used if DS isn't mapped in the shell
[ca_one]
#indent every section for readability
dir = /home/auhorities/ca_one
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ca_two]
dir = /home/auhorities/ca_two
certs = $dir/certs
database = $dir/index.txt
serial = $dir/serial
[ section_test0 ] #this is just how nasty (but valid) things get
space_test= " space " ' test '
var_test_ = boop
var_test.0 = ${var_test_} #local syntax
var_test.1 = $(var_test_) #alt syntax
var_test.2 = ${section_test0::var_test_} #$section::name syntax
var_test.3 = ${ENV::DS} #$ENV::name syntax
dollarid_test = $var_test_ #$dollarid:off only
escape_test = H\C3\A0 N\E1\BB\99i \ \# \\
test_multiline= 123 \\ \ \
456\
#789
Bereinigen Sie nun die Konfigurationsdatei – entfernen Sie Kommentare, Leerzeilen, nachstehende/vorangestellte Leerzeichen und Leerzeichen innerhalb von [ section ]
Beschriftungen und name = value
Paaren.
Ordnen Sie die resultierenden name=value
Paare wie SSL_CONF[section,name]=value
folgt zu:
#!/bin/bash
declare -A SSL_CONF #initialize our config array
declare SSL_CONF_SECTION #set last section
function ssl_include(){
local m a id d="$3" c="${1:-$OPENSSL_CONF}" e='a-zA-Z0-9_' #start in last section. dollarid:false (default). set conf file. regex to match sections
[[ ! -r "$c" ]] && return #the file isn't readable
SSL_CONF_SECTION="${2/ /}" #set section
[ -d "$c" ] && local d='nodir' c="\"${c%/}/*.cnf\" \"${c%/}/*.conf\"" #conf is a dir
while IFS= read -r l || [ -n "$l" ]; do #build SSL_CONF array
l="${l%%[^\\]#*}" #remove comment
if [ "$m" != '' ]; then #last line ended with /
[[ "$l" =~ [^\\]\\$ ]] && l="${l:0:-1}" || m='' #check for continued multiline
SSL_CONF[${SSL_CONF_SECTION// /},${m}]="${SSL_CONF[${SSL_CONF_SECTION// /},${m}]}${l//[^\\]\\$/}" && continue #add current line to last conf and move to next line
fi
l="${l#"${l%%[![:space:]]*}"}"; l="${l%"${l##*[![:space:]]}"}"; [[ "$l" == '' ]] && continue #remove leading/trailing whitespace, then skip empty lines
if [[ "$l" =~ ^\.include[[:space:]]*=[[:space:]]*(.*)$ ]]; then #include additional files
[ "$d" == 'nodir' ] && continue #dir loaded conf files cant include further
local d='no' i="${BASH_REMATCH[1]}" o="${OPENSSL_CONF_INCLUDE:-${c%/*}}" #no variable parsing, store last match, handle default include path
[[ ! "$i" =~ ^\.{0,2}/ ]] && i="${o%/}/${i}" #append default path to relative paths
for f in "$i"; do [ -r "$f" ] && ssl_include "$f" "${SSL_CONF_SECTION// /} " "$d"; done #parse additional configuration files, keeping section
continue
fi
[[ "${SSL_CONF_SECTION// /}" == '' && "$l" =~ ^\.pragma[[:space:]]*=[[:space:]]*(dollarid)[[:space:]]*:[[:space:]]*(true|on)$ ]] && id=${BASH_REMATCH[2]} && continue #see how local variables are parsed
[[ "$l" =~ ^\[[[:space:]]*([${e}]+)[[:space:]]*\]$ ]] && SSL_CONF_SECTION=${BASH_REMATCH[1]} && continue #set section name
if [[ "$l" =~ ^([${e},\;.]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then #name value pair
local n="${BASH_REMATCH[1]}" v="${BASH_REMATCH[2]}"
[[ "$v" =~ [^\\]\\$ ]] && o="$n" #found a multiline value
SSL_CONF[${SSL_CONF_SECTION// /},${n}]="${v//\\[^nbrt\$\\\#]/}" && continue #add name value to SSL_CONF array
fi
done< <(cat $c 2>/dev/null) #loop through the config(s)
}
Die folgende Logik war:
- Kommentare, Leerzeilen, führende/nachgestellte Leerzeichen werden ignoriert
[ section_name ]
undname = value
interne Leerzeichen werden ignoriert.include
und.pragma
erfordern kein=
. Es wurde aus Gründen der Abwärtskompatibilität zugelassen.include
kann sich überall in der Datei befinden.include /dir/
enthält*.cnf
und*.conf
im definierten Verzeichnis. Es deaktiviert.include
die Verarbeitung in den enthaltenen Dateiensection
und$var
Namen können ausa-z
,A-Z
,0-9
, und bestehen._
name
kann bestehen ausa-z
,A-Z
,0-9
,_
,;
,.
, und,
- eine
value
Endung mit einem einzelnen\
(Backslash) Weiter zur nächsten Zeile - Einige Sequenzen müssen
\
folgendermaßen maskiert werden:\\
,\\$
,\#
,\n
,\b
,\r
,\t
Jetzt haben Sie ein assoziatives Array wie SSL_CONF[section,name]=value
. Die .include=(.*)
Dateien werden rekursiv analysiert, sobald sie gefunden werden. .pragma=dollarid:true
wird ebenfalls verarbeitet, sodass Sie Variablen genau analysieren können.
Jetzt haben Sie noch ein letztes Problem:KONFIGURATIONSVARIABLEN. Sie sind derzeit zugewiesen als ${var}
, ${section::var}
, ${ENV::var}
, $var
, $section::var
,$ENV::var
UND $(var)
,, $(section::var)
( $(ENV::var)
wer hätte das gedacht?). Glücklicherweise können wir unser Array durchlaufen SSL_CONF
und die tatsächlichen Werte zuweisen:
#!/bin/bash
[ "$id" != '' ] && a='\{' #ignore not bracketed variables
local f="[^\\\\]*(\\\$(\(|\{|${a})([${e}]+)(::([${e}]+))?(\)|\}|))" # match $var ${var} $(var) unless preceded by \
for k in "${!SSL_CONF[@]}"; do #loop through our array looking for variables
local o #last value placeholder
while [ "${SSL_CONF[$k]}" != "$o" ]; do #only loop if the variable changed
o="${SSL_CONF[$k]}" #set loop to exit on no change
if [[ "${SSL_CONF[$k]}" =~ $f ]] && \
[[ "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '()' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '{}' ]] #brackets match
then #the value contains a variable
local r=' #' #replacement indicator (illegal value)
[[ "$r" == ' #' && ${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]}" #local variable
[[ "$r" == ' #' && ${SSL_CONF[default,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[default,${BASH_REMATCH[3]}]}" #'default' variable
[[ "$r" == ' #' && ${SSL_CONF[,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[,${BASH_REMATCH[3]}]}" #default variable
if [ "${BASH_REMATCH[5]}" != '' ]; then #variable is from another section, default, or ENV
[[ "$r" == ' #' && "${BASH_REMATCH[3]}" == "ENV" ]] && r="${!BASH_REMATCH[5]:-${SSL_CONF[,${BASH_REMATCH[5]}]}}" #environment variable
[[ "$r" == ' #' && ${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]+isset} ]] && r="${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]}" #section variable
fi
[ "$r" != ' #' ] && SSL_CONF[$k]="${SSL_CONF[$k]//${BASH_REMATCH[1]}/$r}" #replace our variable with the value
fi
done
done
Und jetzt sind alle Ihre Variablen ihre berechneten Werte!
Hier ist die vollständige Funktion und der Beispielcode:
#!/bin/bash
declare -A SSL_CONF #initialize our config array
declare SSL_CONF_SECTION #set last section
function ssl_include(){
local m a id d="$3" c="${1:-$OPENSSL_CONF}" e='a-zA-Z0-9_' #start in last section. dollarid:false (default). set conf file. regex to match sections
[[ ! -r "$c" ]] && return #the file isn't readable
SSL_CONF_SECTION="${2/ /}" #set section
[ -d "$c" ] && local d='nodir' c="\"${c%/}/*.cnf\" \"${c%/}/*.conf\"" #conf is a dir
while IFS= read -r l || [ -n "$l" ]; do #build SSL_CONF array
l="${l%%[^\\]#*}" #remove comment
if [ "$m" != '' ]; then #last line ended with /
[[ "$l" =~ [^\\]\\$ ]] && l="${l:0:-1}" || m='' #check for continued multiline
SSL_CONF[${SSL_CONF_SECTION// /},${m}]="${SSL_CONF[${SSL_CONF_SECTION// /},${m}]}${l//[^\\]\\$/}" && continue #add current line to last conf and move to next line
fi
l="${l#"${l%%[![:space:]]*}"}"; l="${l%"${l##*[![:space:]]}"}"; [[ "$l" == '' ]] && continue #remove leading/trailing whitespace, then skip empty lines
if [[ "$l" =~ ^\.include[[:space:]]*=[[:space:]]*(.*)$ ]]; then #include additional files
[ "$d" == 'nodir' ] && continue #dir loaded conf files cant include further
local d='no' i="${BASH_REMATCH[1]}" o="${OPENSSL_CONF_INCLUDE:-${c%/*}}" #no variable parsing, store last match, handle default include path
[[ ! "$i" =~ ^\.{0,2}/ ]] && i="${o%/}/${i}" #append default path to relative paths
for f in "$i"; do [ -r "$f" ] && ssl_include "$f" "${SSL_CONF_SECTION// /} " "$d"; done #parse additional configuration files, keeping section
continue
fi
[[ "${SSL_CONF_SECTION// /}" == '' && "$l" =~ ^\.pragma[[:space:]]*=[[:space:]]*(dollarid)[[:space:]]*:[[:space:]]*(true|on)$ ]] && id=${BASH_REMATCH[2]} && continue #see how local variables are parsed
[[ "$l" =~ ^\[[[:space:]]*([${e}]+)[[:space:]]*\]$ ]] && SSL_CONF_SECTION=${BASH_REMATCH[1]} && continue #set section name
if [[ "$l" =~ ^([${e},\;.]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then #name value pair
local n="${BASH_REMATCH[1]}" v="${BASH_REMATCH[2]}"
[[ "$v" =~ [^\\]\\$ ]] && o="$n" #found a multiline value
SSL_CONF[${SSL_CONF_SECTION// /},${n}]="${v//\\[^nbrt\$\\\#]/}" && continue #add name value to SSL_CONF array
fi
done< <(cat $c 2>/dev/null) #loop through the config(s)
[ "$d" != '' ] && return #don't parse variables in included files, just return the section name
[ "$id" != '' ] && a='\{' #ignore not bracketed variables
local f="[^\\\\]*(\\\$(\(|\{|${a})([${e}]+)(::([${e}]+))?(\)|\}|))" # match $var ${var} $(var) unless preceded by \
for k in "${!SSL_CONF[@]}"; do #loop through our array looking for variables
local o #last value placeholder
while [ "${SSL_CONF[$k]}" != "$o" ]; do #only loop if the variable changed
o="${SSL_CONF[$k]}" #set loop to exit on no change
if [[ "${SSL_CONF[$k]}" =~ $f ]] && \
[[ "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '()' || "${BASH_REMATCH[2]}${BASH_REMATCH[6]}" == '{}' ]] #brackets match
then #the value contains a variable
local r=' #' #replacement indicator (illegal value)
[[ "$r" == ' #' && ${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[${k%%,*},${BASH_REMATCH[3]}]}" #local variable
[[ "$r" == ' #' && ${SSL_CONF[default,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[default,${BASH_REMATCH[3]}]}" #'default' variable
[[ "$r" == ' #' && ${SSL_CONF[,${BASH_REMATCH[3]}]+isset} ]] && r="${SSL_CONF[,${BASH_REMATCH[3]}]}" #default variable
if [ "${BASH_REMATCH[5]}" != '' ]; then #variable is from another section, default, or ENV
[[ "$r" == ' #' && "${BASH_REMATCH[3]}" == "ENV" ]] && r="${!BASH_REMATCH[5]:-${SSL_CONF[,${BASH_REMATCH[5]}]}}" #environment variable
[[ "$r" == ' #' && ${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]+isset} ]] && r="${SSL_CONF[${BASH_REMATCH[3]},${BASH_REMATCH[5]}]}" #section variable
fi
[ "$r" != ' #' ] && SSL_CONF[$k]="${SSL_CONF[$k]//${BASH_REMATCH[1]}/$r}" #replace our variable with the value
fi
done
done
}
Verwendung:ssl_include <file/dir>
Ihre beiden gewünschten Werte befinden sich in ${SSL_CONF[ca_one,certs]}
und ${SSL_CONF[ca_two,certs]}
(das Beispielskript gibt diese Werte wieder).
Kleinere Hinweise:
bash -version
5.03.3(1),openssl version
OpenSSL 1.1.1gwc -l openssl.cnf
824,grep -c '[^\\]=.*[^\\]\$' openssl.cnf
167,time ./ssl_include.sh openssl.cnf
0m0.854s- Wenn
$var
die Zuweisung zum Zeitpunkt der Referenzierung nicht erfolgt,openssl
tritt ein Fehler auf.Das Skript - alle Konfigurationsoptionen, die vor einem Abschnitt gesetzt werden, werden als gesetzt
SSL_CONF[,name]
. Dies vermeidet Kollisionen mit[default]
.[default]
ist aktiviertErste. - jede ungültige Escape-Sequenzsollengelöscht werdenrichtig, AberEs liegt ein OpenSSL-Fehler vor (siehe „Fehler“ in
man config
)das hindert mich daran, zu überprüfen - kann nicht
.pragma
damit arbeitenopenssl
, daher kann ich meine Implementierung nicht überprüfen.kann mir jemand eine funktionierende.pragma
Konfiguration erstellen, damit ich diese Notiz entfernen kann? - noch nicht getestet auf
libressl
, etc openssl
fügt ein.import
in die Datei ein, in der es deklariert wurde. Wenn Ihr Import ein[section]
definiert hat, befinden Sie sich im[section]
weiteren Verlauf darin.Es ist für viele verwirrende Fehler verantwortlich