ohs/apache to nginx

bash -x ./ohs-to-nginx.sh ./ohs_config ./nginx_from_ohs 2>&1 | tee run.log

#!/usr/bin/env bash
# ohs-to-nginx.sh
# Convert *all* Oracle HTTP Server (OHS/Apache httpd) .conf files in a directory
# into ONE CIS-minded nginx.conf. File names are unrestricted as long as they end in .conf.
#
# Multi-instance support:
# - Files may be grouped by a prefix before the first underscore, e.g., OHS1_*.conf, OHS2_*.conf.
# - Files without a prefix belong to the "default" instance.
#
# Output
# - Writes ./nginx_from_ohs/nginx.conf (or to a custom output dir if provided).
#
# Security profile
# - TLS 1.2 ONLY (per requirement) with ECDHE AES-GCM ciphers.
# - CIS-minded http defaults: server_tokens off, hardened timeouts, HSTS, etc.
#
# Usage
#   ./ohs-to-nginx.sh [<ohs_config_dir> [<output_dir>]]
#   example: ./ohs-to-nginx.sh ./ohs_config ./nginx_from_ohs
#
# Notes / Limitations
# - Complex Include graphs, multiple <VirtualHost> blocks, PathTrim/PathPrepend rules are best-effort.
# - This emits at most one HTTP and one HTTPS server per instance. Review & tailor as needed.
#
set -euo pipefail

OHSDIR=${1:-"./ohs_config"}
OUTDIR=${2:-"./nginx_from_ohs"}
OUTFILE="$OUTDIR/nginx.conf"
mkdir -p "$OUTDIR"

# ----------------------------- helpers ---------------------------------------
trim() {
  # usage: trim "  text  " -> prints "text"
  local s=$1
  # strip leading
  s=${s#${s%%[!$'\t\r\n ']*}}
  # strip trailing
  s=${s%${s##*[!$'\t\r\n ']}}
  printf '%s' "$s"
}

lower() { printf '%s' "${1,,}"; }

append_tmp() { TMPFILES+=("$1"); }

declare -a TMPFILES=()
cleanup() { [[ ${#TMPFILES[@]} -gt 0 ]] && rm -f -- "${TMPFILES[@]}" 2>/dev/null || true; }
trap cleanup EXIT ERR INT TERM

# Save original shopt state and enable desired globs locally
save_globs() { SAVED_GLOBS=$(shopt -p nullglob nocaseglob); shopt -s nullglob nocaseglob; }
restore_globs() { eval "$SAVED_GLOBS"; }

# ------------------------ per-instance state (associative) -------------------
declare -A FILES_BY_INST

# core
declare -A LISTEN SERVERNAME DOCROOT ERRLOG ACCLOG
# ssl
declare -A SSL_ON SSL_CERT SSL_KEY SSL_CHAIN SSL_PROTOCOLS SSL_CIPHERS
# weblogic
declare -A WL_MODE WL_CLUSTER WL_SINGLE_HOST WL_SINGLE_PORT WL_TIMEOUT WL_KEEPALIVE WL_LOC_TMP

init_instance() {
  local inst=$1
  LISTEN[$inst]=""
  SERVERNAME[$inst]=""
  DOCROOT[$inst]=""
  ERRLOG[$inst]=""
  ACCLOG[$inst]=""
  SSL_ON[$inst]=0
  SSL_CERT[$inst]=""
  SSL_KEY[$inst]=""
  SSL_CHAIN[$inst]=""
  SSL_PROTOCOLS[$inst]=""
  SSL_CIPHERS[$inst]=""
  WL_MODE[$inst]="none"
  WL_CLUSTER[$inst]=""
  WL_SINGLE_HOST[$inst]=""
  WL_SINGLE_PORT[$inst]=""
  WL_TIMEOUT[$inst]=""
  WL_KEEPALIVE[$inst]=""
  local t
  t=$(mktemp)
  append_tmp "$t"
  WL_LOC_TMP[$inst]="$t"
}

# ----------------------------- discover files --------------------------------
# Recursively gather all *.conf files under OHSDIR
save_globs
CONF_LIST=$(find "$OHSDIR" -type f -name '*.conf' 2>/dev/null | sort || true)
restore_globs

if [[ -z ${CONF_LIST} ]]; then
  echo "No .conf files found under: $OHSDIR" >&2
  # still create an empty scaffold nginx.conf to make behavior explicit
  mkdir -p "$OUTDIR"
  cat >"$OUTDIR/nginx.conf" <<'EMPTY'
# Empty nginx.conf generated: no .conf files discovered in the source directory.
# Verify your input path and ensure files end with .conf (recursively searched).
EMPTY
  echo "Wrote: $OUTDIR/nginx.conf (empty scaffold)" >&2
  exit 0
fi

# Bucket files by instance
while IFS= read -r f; do
  base=$(basename -- "$f")
  lbase=$(lower "$base")
  inst="default"
  if [[ "$lbase" == *_*.conf ]]; then
    inst=${lbase%%_*}
  fi
  if [[ -n ${FILES_BY_INST[$inst]:-} ]]; then
    FILES_BY_INST[$inst]+=$'
'"$f"
  else
    FILES_BY_INST[$inst]="$f"
  fi
done <<< "$CONF_LIST"

if [[ ${#FILES_BY_INST[@]} -eq 0 ]]; then
  echo "No .conf files found under: $OHSDIR" >&2
  exit 1
fi

# ----------------------------- parsing ---------------------------------------
parse_conf_file() {
  local inst=$1 file=$2 inloc=0 lpath="" matchExpr="" sawWL=0
  while IFS= read -r raw || [[ -n $raw ]]; do
    local line=${raw%%#*}
    line=$(trim "$line")
    [[ -z $line ]] && continue

    # <Location ...> tracking for weblogic-handler
    if [[ $line =~ ^<Location[[:space:]]+ ]]; then
      inloc=1
      lpath=${line#<Location }
      lpath=${lpath%>}
      matchExpr=""; sawWL=0
      continue
    fi
    if (( inloc==1 )) && [[ $line =~ ^</Location> ]]; then
      if (( sawWL==1 )); then
        if [[ -n $matchExpr ]]; then
          printf '%s\t%s\t%s\n' "$lpath" "regex" "$matchExpr" >>"${WL_LOC_TMP[$inst]}"
        else
          printf '%s\t%s\t%s\n' "$lpath" "path" "$lpath" >>"${WL_LOC_TMP[$inst]}"
        fi
      fi
      inloc=0; lpath=""; matchExpr=""; sawWL=0
      continue
    fi
    if (( inloc==1 )); then
      case "${line,,}" in
        sethandler\ weblogic-handler) sawWL=1 ;;
        matchexpression*) matchExpr=${line#* } ;;
      esac
      continue
    fi

    # flat directives
    local key=${line%% *}
    local rest=""; [[ "$line" == *" "* ]] && rest=${line#* }
    case "${key,,}" in
      listen)
        # handle "IP:PORT" or "PORT"
        local prt=${rest##*:}
        prt=$(trim "$prt")
        LISTEN[$inst]="$prt" ;;
      servername)
        SERVERNAME[$inst]="$(trim "$rest")" ;;
      documentroot)
        DOCROOT[$inst]="${rest%\"}"; DOCROOT[$inst]="${DOCROOT[$inst]#\"}" ;;
      errorlog)
        ERRLOG[$inst]="$(trim "$rest")" ;;
      customlog)
        ACCLOG[$inst]="$(printf '%s' "$rest" | awk '{print $1}')" ;;
      # SSL
      sslcertificatefile)
        SSL_ON[$inst]=1; SSL_CERT[$inst]="$(trim "$rest")" ;;
      sslcertificatekeyfile)
        SSL_ON[$inst]=1; SSL_KEY[$inst]="$(trim "$rest")" ;;
      sslcertificatechainfile)
        SSL_ON[$inst]=1; SSL_CHAIN[$inst]="$(trim "$rest")" ;;
      sslprotocol)
        SSL_ON[$inst]=1; SSL_PROTOCOLS[$inst]="$(trim "$rest")" ;;
      sslciphersuite)
        SSL_ON[$inst]=1; SSL_CIPHERS[$inst]="$(trim "$rest")" ;;
      # WebLogic
      weblogiccluster)
        WL_MODE[$inst]="cluster"; WL_CLUSTER[$inst]="$(trim "$rest" | tr -d '"')" ;;
      weblogichost)
        WL_SINGLE_HOST[$inst]="$(trim "$rest" | tr -d '"')" ;;
      weblogicport)
        WL_SINGLE_PORT[$inst]="$(trim "$rest" | tr -d '"')" ;;
      connecttimeoutsecs)
        WL_TIMEOUT[$inst]="$(trim "$rest")" ;;
      keepaliveenabled)
        WL_KEEPALIVE[$inst]="$(lower "$(trim "$rest")")" ;;
    esac
  done < "$file"
}

# init + parse per instance
INSTANCES=()
for inst in "${!FILES_BY_INST[@]}"; do INSTANCES+=("$inst"); init_instance "$inst"; done

for inst in "${INSTANCES[@]}"; do
  while IFS= read -r f; do parse_conf_file "$inst" "$f"; done < <(printf '%s\n' "${FILES_BY_INST[$inst]}")
  # derive single-host mode if host+port present and no cluster
  if [[ ${WL_MODE[$inst]} == "none" && -n ${WL_SINGLE_HOST[$inst]} && -n ${WL_SINGLE_PORT[$inst]} ]]; then
    WL_MODE[$inst]="single"
  fi
  [[ -z ${LISTEN[$inst]} ]] && LISTEN[$inst]=80
done

# ----------------------------- security profile ------------------------------
CIS_TLS12_CIPHERS='ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'

# ----------------------------- emit nginx.conf -------------------------------
{
  printf '# Generated by ohs-to-nginx.sh on %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  printf '# Source: %s\n\n' "$OHSDIR"
  cat <<'HDR'
user  nginx;
worker_processes  auto;

events {
    worker_connections  10240;
}

http {
    # --- CIS-minded global defaults ---
    server_tokens off;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile      on;
    tcp_nopush    on;
    tcp_nodelay   on;
    keepalive_timeout  15;
    client_body_timeout  15;
    client_header_timeout 15;
    send_timeout         15;
    client_max_body_size 10m;
    access_log  /var/log/nginx/access.log;
    error_log   /var/log/nginx/error.log warn;
HDR

  for inst in "${INSTANCES[@]}"; do
    iname=$inst; [[ $iname == "default" ]] && iname="ohs"

    # upstream
    if [[ ${WL_MODE[$inst]} != "none" ]]; then
      printf '    upstream %s_weblogic {\n' "$iname"
      if [[ ${WL_MODE[$inst]} == "cluster" ]]; then
        IFS=',' read -r -a parts <<< "${WL_CLUSTER[$inst]}"
        for hp in "${parts[@]}"; do
          hp=$(trim "$hp"); [[ -z $hp ]] && continue
          printf '        server %s;\n' "$hp"
        done
      else
        printf '        server %s:%s;\n' "${WL_SINGLE_HOST[$inst]}" "${WL_SINGLE_PORT[$inst]}"
      fi
      printf '        keepalive 64;\n'
      printf '    }\n\n'
    fi

    # HTTP :80
    printf '    server {\n'
    printf '        listen 80;\n'
    if [[ -n ${SERVERNAME[$inst]} ]]; then
      printf '        server_name %s;\n' "${SERVERNAME[$inst]}"
    else
      printf '        server_name _;\n'
    fi
    if [[ -n ${DOCROOT[$inst]} ]]; then
      printf '        root %s;\n' "${DOCROOT[$inst]}"
    else
      printf '        # root /usr/share/nginx/html;\n'
    fi

    cat <<'SHEAD'
        add_header X-Content-Type-Options nosniff always;
        add_header X-Frame-Options SAMEORIGIN always;
        add_header Referrer-Policy no-referrer-when-downgrade always;
SHEAD

    # optional logs reference
    [[ -n ${ERRLOG[$inst]} ]] && printf '        # error_log  %s;\n' "${ERRLOG[$inst]}"
    [[ -n ${ACCLOG[$inst]} ]] && printf '        # access_log %s;\n' "${ACCLOG[$inst]}"

    # locations → upstream
    if [[ ${WL_MODE[$inst]} != "none" && -s ${WL_LOC_TMP[$inst]} ]]; then
      proxy_tout=${WL_TIMEOUT[$inst]:-60}
      keepal=${WL_KEEPALIVE[$inst]:-on}
      if [[ $keepal == on ]]; then ka_comment="#"; else ka_comment=""; fi
      while IFS=$'\t' read -r lpath ltype lpat; do
        [[ -z $lpath ]] && continue
        if [[ $ltype == regex ]]; then
          printf '        location ~ %s {\n' "$lpat"
        else
          printf '        location %s {\n' "$lpath"
        fi
        cat <<LOC
            proxy_set_header Host \$host;
            proxy_set_header X-Real-IP \$remote_addr;
            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto \$scheme;
            proxy_http_version 1.1;
            ${ka_comment}proxy_set_header Connection "\$connection_upgrade"; # keepalive hint
            proxy_connect_timeout ${proxy_tout}s;
            proxy_read_timeout ${proxy_tout}s;
            proxy_send_timeout ${proxy_tout}s;
            proxy_pass http://${iname}_weblogic;
            # TODO: If OHS PathTrim/PathPrepend was used, add equivalent rewrite rules.
        }
LOC
      done < "${WL_LOC_TMP[$inst]}"
    else
      printf '        # No mod_wl_ohs upstream detected for instance "%s"\n' "$inst"
    fi

    printf '    }\n\n'

    # HTTPS :443
    if [[ ${SSL_ON[$inst]} -eq 1 ]]; then
      printf '    server {\n'
      printf '        listen 443 ssl;\n'
      if [[ -n ${SERVERNAME[$inst]} ]]; then
        printf '        server_name %s;\n' "${SERVERNAME[$inst]}"
      else
        printf '        server_name _;\n'
      fi
      if [[ -n ${DOCROOT[$inst]} ]]; then
        printf '        root %s;\n' "${DOCROOT[$inst]}"
      else
        printf '        # root /usr/share/nginx/html;\n'
      fi
      printf '        ssl_protocols TLSv1.2;\n'
      printf '        ssl_ciphers %s;\n' "$CIS_TLS12_CIPHERS"
      cat <<'TLSA'
        ssl_prefer_server_ciphers on;
        ssl_session_timeout 10m;
        ssl_session_cache shared:SSL:20m;
        ssl_session_tickets off;
TLSA
      if [[ -n ${SSL_CERT[$inst]} && -n ${SSL_KEY[$inst]} ]]; then
        printf '        ssl_certificate     %s;\n' "${SSL_CERT[$inst]}"
        printf '        ssl_certificate_key %s;\n' "${SSL_KEY[$inst]}"
      else
        printf '        # ssl_certificate /path/to/fullchain.pem;\n'
        printf '        # ssl_certificate_key /path/to/privkey.pem;\n'
      fi
      if [[ -n ${SSL_CHAIN[$inst]} ]]; then
        printf '        # (Chain typically bundled into ssl_certificate)  # %s\n' "${SSL_CHAIN[$inst]}"
      fi

      cat <<'HSTS'
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-Frame-Options SAMEORIGIN always;
        add_header Referrer-Policy no-referrer-when-downgrade always;
HSTS

      if [[ ${WL_MODE[$inst]} != "none" && -s ${WL_LOC_TMP[$inst]} ]]; then
        proxy_tout=${WL_TIMEOUT[$inst]:-60}
        keepal=${WL_KEEPALIVE[$inst]:-on}
        if [[ $keepal == on ]]; then ka_comment="#"; else ka_comment=""; fi
        while IFS=$'\t' read -r lpath ltype lpat; do
          [[ -z $lpath ]] && continue
          if [[ $ltype == regex ]]; then
            printf '        location ~ %s {\n' "$lpat"
          else
            printf '        location %s {\n' "$lpath"
          fi
          cat <<LOC2
            proxy_set_header Host \$host;
            proxy_set_header X-Real-IP \$remote_addr;
            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto \$scheme;
            proxy_http_version 1.1;
            ${ka_comment}proxy_set_header Connection "\$connection_upgrade"; # keepalive hint
            proxy_connect_timeout ${proxy_tout}s;
            proxy_read_timeout ${proxy_tout}s;
            proxy_send_timeout ${proxy_tout}s;
            proxy_pass http://${iname}_weblogic;
        }
LOC2
        done < "${WL_LOC_TMP[$inst]}"
      fi

      printf '    }\n\n'
    fi
  done
  printf '}\n'
} >"$OUTFILE"

# ----------------------------- README ----------------------------------------
cat > "$OUTDIR/README.txt" <<'EOT'
This nginx.conf was auto-generated from Oracle HTTP Server configs.

Key properties
- Reads ALL *.conf files from the provided directory; names are unrestricted.
- Groups files by instance prefix before the first underscore; unprefixed => "default".
- CIS-minded HTTP defaults; TLS 1.2 only with ECDHE AES-GCM cipher suites.

Review checklist
1) Verify upstream servers/ports (under upstream <instance>_weblogic).
2) Confirm server_name/root and log paths.
3) If OHS used PathTrim/PathPrepend/MatchExpression, add equivalent rewrites.
4) Check certificate/key paths and bundle chain as needed.
5) Validate and reload: nginx -t && systemctl reload nginx
EOT

printf 'Wrote: %s\n' "$OUTFILE"
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments