diff --git a/Caddyfile.example b/Caddyfile.example index 311c374..b6bbc6b 100644 --- a/Caddyfile.example +++ b/Caddyfile.example @@ -3,15 +3,15 @@ # Replace "__ACCOUNT_NAME__" with a desired storage account name if needed __ACCOUNT_NAME__.blob.core.windows.net { tls __AZURITE_DIR__/__ACCOUNT_NAME___cert.pem __AZURITE_DIR__/__ACCOUNT_NAME___key.pem - reverse_proxy localhost:10000 + reverse_proxy 127.0.0.1:10000 } __ACCOUNT_NAME__.queue.core.windows.net { tls __AZURITE_DIR__/__ACCOUNT_NAME___cert.pem __AZURITE_DIR__/__ACCOUNT_NAME___key.pem - reverse_proxy localhost:10001 + reverse_proxy 127.0.0.1:10001 } __ACCOUNT_NAME__.table.core.windows.net { tls __AZURITE_DIR__/__ACCOUNT_NAME___cert.pem __AZURITE_DIR__/__ACCOUNT_NAME___key.pem - reverse_proxy localhost:10002 + reverse_proxy 127.0.0.1:10002 } diff --git a/Dockerfile b/Dockerfile index e176724..f38503b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ RUN npm pkg set scripts.prepare="echo no-prepare" RUN npm ci --omit=dev --unsafe-perm WORKDIR /app +COPY ./cert-functions.sh ./ COPY ./entrypoint.sh . COPY ./Caddyfile.example . RUN chmod +x entrypoint.sh diff --git a/cert-functions.sh b/cert-functions.sh new file mode 100644 index 0000000..457fb26 --- /dev/null +++ b/cert-functions.sh @@ -0,0 +1,63 @@ +function make_ca() { + # Use the provided directory argument or default to AZURITE_DIR if not provided + local CERT_DIR="${1:-$AZURITE_DIR}" + + if [[ ! -d "$CERT_DIR" ]]; then + echo "ERROR: Certificate directory $CERT_DIR does not exist." + return 1 + fi + + # Generate CA certificate and key if they don't exist + if [[ ! -f "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then + echo "Generating CA certificate and key..." + if ! openssl req \ + -x509 \ + -newkey rsa:4096 \ + -keyout "$CERT_DIR/ca_key.pem" \ + -out "$CERT_DIR/ca_cert.pem" \ + -days 3650 \ + -nodes \ + -subj "/CN=Azurite CA" \ + -text \ + -addext "basicConstraints=critical,CA:TRUE,pathlen:0"; then + echo "Error: Failed to generate CA certificate and key." >&2 + return 1 + fi + fi +} + +function make_server_cert() { + local ACCOUNT_NAME="${1:-devstoreaccount1}" + local CERT_DIR="${2:-$AZURITE_DIR}" + + if [[ ! -d "$CERT_DIR" ]]; then + echo "ERROR: Certificate directory $CERT_DIR does not exist." + return 1 + fi + + # Generate server certificate and key if they don't exist + if [[ ! -f "$CERT_DIR/${ACCOUNT_NAME}_cert.pem" || ! -f "$CERT_DIR/${ACCOUNT_NAME}_key.pem" ]]; then + echo "Generating server certificate and key..." + if ! openssl req \ + -newkey rsa:4096 \ + -keyout "$CERT_DIR/${ACCOUNT_NAME}_key.pem" \ + -nodes \ + -subj "/CN=${ACCOUNT_NAME}.blob.core.windows.net" \ + -addext "basicConstraints=critical,CA:FALSE" \ + -addext "keyUsage=digitalSignature,keyEncipherment" \ + -addext "extendedKeyUsage=serverAuth,clientAuth" \ + -addext "subjectAltName=DNS:${ACCOUNT_NAME}.blob.core.windows.net,DNS:${ACCOUNT_NAME}.queue.core.windows.net,DNS:${ACCOUNT_NAME}.table.core.windows.net,DNS:localhost,IP:127.0.0.1" \ + | openssl x509 \ + -req \ + -CA "$CERT_DIR/ca_cert.pem" \ + -CAkey "$CERT_DIR/ca_key.pem" \ + -set_serial "0x$(openssl rand -hex 16)" \ + -copy_extensions copyall \ + -days 365 \ + -text \ + -out "$CERT_DIR/${ACCOUNT_NAME}_cert.pem"; then + echo "Error: Failed to generate server certificate and key." >&2 + exit 1 + fi + fi +} diff --git a/entrypoint.sh b/entrypoint.sh index 89a2e63..1ffc243 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,69 +2,8 @@ set -e -function make_ca() { - # Use the provided directory argument or default to AZURITE_DIR if not provided - local CERT_DIR="${1:-$AZURITE_DIR}" - - if [[ ! -d "$CERT_DIR" ]]; then - echo "ERROR: Certificate directory $CERT_DIR does not exist." - return 1 - fi - - # Generate CA certificate and key if they don't exist - if [[ ! -f "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then - echo "Generating CA certificate and key..." - if ! openssl req \ - -x509 \ - -newkey rsa:4096 \ - -keyout "$CERT_DIR/ca_key.pem" \ - -out "$CERT_DIR/ca_cert.pem" \ - -days 3650 \ - -nodes \ - -subj "/CN=Azurite CA" \ - -text \ - -addext "basicConstraints=critical,CA:TRUE,pathlen:0"; then - echo "Error: Failed to generate CA certificate and key." >&2 - return 1 - fi - fi -} - -function make_server_cert() { - local ACCOUNT_NAME="${1:-devstoreaccount1}" - local CERT_DIR="${2:-$AZURITE_DIR}" - - if [[ ! -d "$CERT_DIR" ]]; then - echo "ERROR: Certificate directory $CERT_DIR does not exist." - return 1 - fi - - # Generate server certificate and key if they don't exist - if [[ ! -f "$CERT_DIR/${ACCOUNT_NAME}_cert.pem" || ! -f "$CERT_DIR/${ACCOUNT_NAME}_key.pem" ]]; then - echo "Generating server certificate and key..." - if ! openssl req \ - -newkey rsa:4096 \ - -keyout "$CERT_DIR/${ACCOUNT_NAME}_key.pem" \ - -nodes \ - -subj "/CN=${ACCOUNT_NAME}.blob.core.windows.net" \ - -addext "basicConstraints=critical,CA:FALSE" \ - -addext "keyUsage=digitalSignature,keyEncipherment" \ - -addext "extendedKeyUsage=serverAuth,clientAuth" \ - -addext "subjectAltName=DNS:${ACCOUNT_NAME}.blob.core.windows.net,DNS:${ACCOUNT_NAME}.queue.core.windows.net,DNS:${ACCOUNT_NAME}.table.core.windows.net,DNS:localhost,IP:127.0.0.1" \ - | openssl x509 \ - -req \ - -CA "$CERT_DIR/ca_cert.pem" \ - -CAkey "$CERT_DIR/ca_key.pem" \ - -set_serial "0x$(openssl rand -hex 16)" \ - -copy_extensions copyall \ - -days 365 \ - -text \ - -out "$CERT_DIR/${ACCOUNT_NAME}_cert.pem"; then - echo "Error: Failed to generate server certificate and key." >&2 - exit 1 - fi - fi -} +# Include certificate generation functions. +. /app/cert-functions.sh # Setup default storage location, account name and accounts file path. AZURITE_DIR="/storage" @@ -139,11 +78,11 @@ if [[ -n "$CADDY" ]]; then echo "Starting Caddy server..." caddy start --config "$AZURITE_DIR/Caddyfile" # Use start not run, start does not block the shell process. else - # If not using Caddy, configure Azurite to listen on all interfaces and use the generated self-signed certificate. - # This mode does not require Caddy, but it will not allow simultaneous access to the table and queue services on the same HTTPS port. - if [[ -n "$AZURITE_SSL" ]]; then - CERT_ARGS=("--key" "$AZURITE_DIR/${ACCOUNT_NAME}_key.pem" "--cert" "$AZURITE_DIR/${ACCOUNT_NAME}_cert.pem") - fi + # If not using Caddy, configure Azurite to listen on all interfaces and use the generated self-signed certificate. + # This mode does not require Caddy, but it will not allow simultaneous access to the table and queue services on the same HTTPS port. + if [[ -n "$AZURITE_SSL" ]]; then + CERT_ARGS=("--key" "$AZURITE_DIR/${ACCOUNT_NAME}_key.pem" "--cert" "$AZURITE_DIR/${ACCOUNT_NAME}_cert.pem") + fi fi # Start Azurite with the appropriate arguments based on the configuration. diff --git a/run-server.sh b/run-server.sh index 1643042..06e2ab6 100755 --- a/run-server.sh +++ b/run-server.sh @@ -1,67 +1,124 @@ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Include certificate generation functions. +. "$SCRIPT_DIR/cert-functions.sh" + AZURITE_DIR="$SCRIPT_DIR/storage" if [[ ! -d "$AZURITE_DIR" ]]; then - echo "No accounts found" - exit 0 + echo "No accounts found" + exit 0 +fi + +if [[ -f "$SCRIPT_DIR/accounts.env" ]]; then + set -a + . "$SCRIPT_DIR/accounts.env" + set +a +fi + +# Look up the account name from the AZURITE_ACCOUNTS variable, which is in the format "accountName:accountKey1:accountKey2;accountName2:accountKey1:accountKey2" +ACCOUNT_NAME=$(echo "$AZURITE_ACCOUNTS" | cut -f 1 -d ';' | cut -f 1 -d ':') + +# Ensure /etc/hosts contains an entry the Azure endpoint names, +# so Caddy can route requests to the correct service based on the hostname. +if ! grep -qE "${ACCOUNT_NAME}\.blob\.core\.windows\.net" /etc/hosts || + ! grep -qE "${ACCOUNT_NAME}\.queue\.core\.windows\.net" /etc/hosts || + ! grep -qE "${ACCOUNT_NAME}\.table\.core\.windows\.net" /etc/hosts; then + echo "ERROR: /etc/hosts does not contain entries for the Azure endpoints. Please ensure the following line is present in /etc/hosts:" + echo -e "\n127.0.0.1\t${ACCOUNT_NAME}.blob.core.windows.net ${ACCOUNT_NAME}.queue.core.windows.net ${ACCOUNT_NAME}.table.core.windows.net" + exit 1 fi if ! command -v azurite &> /dev/null; then - echo "Azurite is not installed. Please install it with 'npm install -g azurite'" - exit 1 + echo "Azurite is not installed. Please install it with 'npm install -g azurite'" + exit 1 fi if command -v caddy &> /dev/null; then - CADDY=true + CADDY=true else - CADDY="" + CADDY="" fi +AZURITE_SSL="" +CERT_ARGS=() OAUTH_ARGS=() +PORTS_ARGS=("--blobPort" "10000" "--queuePort" "10001" "--tablePort" "10002") while [[ $# -gt 0 ]]; do - case "$1" in - --oauth) - OAUTH_ARGS=("--oauth" "basic") - shift - ;; - --no-caddy) - CADDY="" - shift - ;; - *) - echo "Unknown argument: $1" - exit 1 - ;; - esac + case "$1" in + --oauth) + case "$2" in + blob) + PORTS_ARGS=("--blobPort" "443" "--queuePort" "10001" "--tablePort" "10002") + ;; + queue) + PORTS_ARGS=("--blobPort" "10000" "--queuePort" "443" "--tablePort" "10002") + ;; + table) + PORTS_ARGS=("--blobPort" "10000" "--queuePort" "10001" "--tablePort" "443") + ;; + *) + echo "Error: --oauth must be followed by 'blob', 'queue', or 'table'." >&2 + exit 1 + ;; + esac + OAUTH_ARGS=("--oauth" "basic") + # Ensure Caddy is disabled when using OAuth, as Azurite does not support OAuth behind a reverse proxy. + CADDY="" + AZURITE_SSL=true + shift 2 + ;; + --ssl) + AZURITE_SSL=true + # Ensure Caddy is disabled when using Azurite's built-in SSL support. + CADDY="" + shift + ;; + --no-caddy) + # Disable Caddy, but do not enable SSL for Azurite. + CADDY="" + shift + ;; + *) + echo "Unknown argument: $1" + exit 1 + ;; + esac done -AZURITE_ACCOUNTS_FILE="$AZURITE_DIR/accounts.env" -set -a -. $AZURITE_ACCOUNTS_FILE -set +a +# Ensure certificates are generated before starting Azurite or Caddy. +if [[ -n "$AZURITE_SSL" || -n "$CADDY" ]]; then + if ! make_ca "$AZURITE_DIR"; then + echo "Error: Failed to create CA certificate and key." >&2 + exit 1 + fi + + if ! make_server_cert "$ACCOUNT_NAME" "$AZURITE_DIR"; then + echo "Error: Failed to create server certificate and key." >&2 + exit 1 + fi +fi if [[ -n "$CADDY" ]]; then - # If using Caddy, it will reverse proxy blob, queue, and table endpoints to different ports on localhost, - # allowing simultaneous access to all services on a single HTTPS port. - echo "Starting Caddy server..." - caddy start --config Caddyfile # Use start not run, start does not block the shell process. - trap "echo 'Stopping Caddy server...'; caddy stop" EXIT INT TERM HUP KILL STOP - CERT_ARGS=() - BLOB_ARGS=() - OAUTH_ARGS=() # Ensure OAuth is disabled when using Caddy, as Azurite does not support OAuth in reverse proxy mode. + # If using Caddy, it will reverse proxy blob, queue, and table endpoints to different ports on localhost, + # allowing simultaneous access to all services on a single HTTPS port. + # Generate a Caddyfile configuration based on the account name and storage directory. + sed -E "s/__ACCOUNT_NAME__/${ACCOUNT_NAME}/g; s|__AZURITE_DIR__|${AZURITE_DIR}|g" "$SCRIPT_DIR/Caddyfile.example" > "$AZURITE_DIR/Caddyfile" + echo "Starting Caddy server..." + caddy start --config "$AZURITE_DIR/Caddyfile" # Use start not run, start does not block the shell process. + trap "echo 'Stopping Caddy server...'; caddy stop" EXIT INT TERM HUP KILL STOP + CERT_ARGS=() + OAUTH_ARGS=() # Ensure OAuth is disabled when using Caddy, as Azurite does not support OAuth in reverse proxy mode. else - # If not using Caddy, configure Azurite to listen on all interfaces and use the generated self-signed certificate. - # This mode does not require Caddy, but it will not allow simultaneous access to the table and queue services on the same HTTPS port. - BLOB_ARGS=("--blobHost" "0.0.0.0" "--blobPort" "443") - CERT_ARGS=("--key" "$AZURITE_DIR/server_key.pem" "--cert" "$AZURITE_DIR/server_cert.pem") + # If not using Caddy, configure Azurite to listen on all interfaces and use the generated self-signed certificate. + # This mode does not require Caddy, but it will not allow simultaneous access to the table and queue services on the same HTTPS port. + CERT_ARGS=("--key" "$AZURITE_DIR/server_key.pem" "--cert" "$AZURITE_DIR/server_cert.pem") fi azurite \ --disableTelemetry \ --location "$AZURITE_DIR" \ - "${CERT_ARGS[@]}" \ - "${BLOB_ARGS[@]}" \ - "${OAUTH_ARGS[@]}" + --blobHost 127.0.0.1 --queueHost 127.0.0.1 --tableHost 127.0.0.1 \ + "${PORTS_ARGS[@]}" "${CERT_ARGS[@]}" "${OAUTH_ARGS[@]}"