Я пытаюсь найти подсказку или решение для чтения значений конфигурации openssl в скрипте оболочки. Позвольте мне предоставить вам немного больше подробностей.
у меня естьopenssl.confфайл со следующим упрощенным содержанием:
[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
Я пишу bash-скрипт, который будет просматривать каталог dir/certs для каждого центра сертификации, т.е. мне нужно получить значения/home/auhorities/ca_one/certsи/home/auhorities/ca_two/certsформируем файл. На данный момент я пришел к следующему решению:
#!/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
Однако, боюсь, что это не идеально, если функционал скрипта будет обновлен в будущем. Я ищу решение, которое позволит мне или моим коллегам в будущем перемещаться по записям центра сертификации более удобным способом. Например, я нашел opensslконфигурацияруководство(https://www.openssl.org/docs/manmaster/man5/config.html), в котором говорится, чтоконфигурациябиблиотека "может использоваться для чтения файлов конфигурации". Насколько я понимаю, эта библиотека используется внутренне такими инструментами openssl, какок, требованиеили другое. Возможно ли тогда прочитать записи из файла конфигурации с помощью некоторых утилит openssl? Я, вероятно, что-то упускаю, но я просто не могу понять, возможно это или нет. Если это не вариант, как бы вы справились с такой ситуацией?
Заранее спасибо.
решение1
Официального инструмента командной строки для разбора или проверки openssl.cnf нет. bash
Однако я создал совместимую функцию.человек конфигурацияэто определенно то, что вам нужно читать и перечитывать.
ВАЖНО: Если ваша конфигурация повреждена, это не сработает. Исправьте это!
Пример openssl.cnf
должен быть немного сложнее для тестирования:
#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
Теперь очистите файл конфигурации — удалите комментарии, пустые строки, начальные и конечные пробелы, а также пробелы внутри [ section ]
меток и name = value
пар.
Назначьте все полученные name=value
пары SSL_CONF[section,name]=value
следующим образом:
#!/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)
}
Логика была следующей:
- комментарии, пустые строки, начальные/конечные пробелы игнорируются
[ section_name ]
иname = value
внутренние пробелы игнорируются.include
и.pragma
не требуют=
. Это было разрешено для обратной совместимости.include
может быть в любом месте файла.include /dir/
включает*.cnf
и*.conf
в указанном каталоге. Отключает.include
обработку во включенных файлахsection
и$var
имена могут состоять изa-z
,A-Z
,0-9
, и_
name
может состоять изa-z
,A-Z
,0-9
,_
,;
,.
, и,
- окончание
value
в одинарном\
(обратная косая черта) продолжении на следующей строке - некоторые последовательности требуют экранирования,
\
например, так:\\
,\\$
,\#
,\n
,\b
,\r
,\t
Теперь у вас есть ассоциативный массив, например SSL_CONF[section,name]=value
. .include=(.*)
Файлы анализируются рекурсивно, как только они встречаются. .pragma=dollarid:true
также обрабатывается, чтобы вы могли точно анализировать переменные.
Теперь у вас есть еще одна последняя проблема:ПЕРЕМЕННЫЕ КОНФИГУРАЦИИ. В настоящее время они назначены как ${var}
, ${section::var}
, ${ENV::var}
, $var
, $section::var
,$ENV::var
И $(var)
, $(section::var)
, $(ENV::var)
(кто знал?). К счастью, мы можем пройтись по нашему SSL_CONF
массиву и присвоить реальные значения:
#!/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
И теперь все ваши переменные — это их вычисленные значения!
Вот полная функция и пример кода:
#!/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
}
Использование:ssl_include <file/dir>
Ваши два желаемых значения находятся в ${SSL_CONF[ca_one,certs]}
и ${SSL_CONF[ca_two,certs]}
(пример скрипта повторяет эти значения).
Незначительные примечания:
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
0м0.854с- Если
$var
к моменту обращения к нему не назначено,openssl
произойдет сбой.Сценарий не - все параметры конфигурации, заданные перед любым разделом, устанавливаются как
SSL_CONF[,name]
. Это позволяет избежать конфликтов с[default]
.[default]
отмеченопервый. - любая недопустимая последовательность переходадолженбыть удаленправильно, ноесть ошибка openssl (см. «Ошибки» в
man config
)что мешает мне проверить - не могу
.pragma
работать сopenssl
, поэтому не могу проверить свою реализацию.может ли кто-нибудь сделать мне рабочую.pragma
конфигурацию, чтобы я мог удалить эту заметку? - еще не тестировалось на
libressl
и т.д. openssl
вставляет.import
в файл, где он был объявлен. Если ваш импорт имеет[section]
определенный, вы находитесь в этом[section]
движении вперед.Он является причиной многих запутанных ошибок.