Чтение конфигурации из файла конфигурации openssl

Чтение конфигурации из файла конфигурации openssl

Я пытаюсь найти подсказку или решение для чтения значений конфигурации 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 -version5.03.3(1), openssl versionOpenSSL 1.1.1g
  • wc -l openssl.cnf824, grep -c '[^\\]=.*[^\\]\$' openssl.cnf167,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]движении вперед.Он является причиной многих запутанных ошибок.

Связанный контент