Compare commits

...

24 Commits

Author SHA1 Message Date
8d967d7ad7 fix: remove --oauth command from azurite service in compose.yaml and enhance error handling in run-server.sh 2026-03-24 11:29:12 +01:00
f825835744 fix: remove azurite deployment and service configuration from Kubernetes manifests 2026-03-24 11:28:45 +01:00
ea6277ba3b update: refactor run-server.sh to match Docker version. 2026-03-24 11:17:10 +01:00
8800154d37 fix: update image handling in build script to use AZURITE_IMAGE variable consistently 2026-03-24 10:23:32 +01:00
8c454fa2fc fix: update Caddyfile generation and add /etc/hosts entries for emulator endpoints 2026-03-24 09:58:35 +01:00
149f8a30eb fix: enhance argument handling in start-azurite script to support container configuration 2026-03-24 09:47:14 +01:00
2631d4ff85 fix: allow unknown arguments in start-azurite script to be passed as container arguments 2026-03-24 09:45:44 +01:00
b342be88f3 fix: update start-azurite script to improve error handling and streamline container arguments 2026-03-24 09:43:40 +01:00
795e1c6740 fix: improve error messaging and correct NO_CADDY condition in entrypoint.sh 2026-03-24 09:43:29 +01:00
a19036f10d fix: simplify AZURITE_ACCOUNTS check by removing default account generation 2026-03-24 09:38:52 +01:00
4c66ee38ba fix: move account name lookup above certificate check in entrypoint.sh 2026-03-24 09:31:46 +01:00
7f1027c6e0 fix: add check for existence of account certificate and key in entrypoint.sh 2026-03-24 09:18:28 +01:00
9d1546a1bc fix: update Dockerfile to copy the correct Caddyfile template and remove unused cert-functions.sh 2026-03-24 08:30:09 +01:00
00459cbb1c refactor: remove unused certificate generation functions and streamline entrypoint.sh 2026-03-24 08:29:01 +01:00
2af06a3bbf fix: update CA_DIR default value to use ./storage instead of ./ca 2026-03-24 08:28:47 +01:00
b092c00e27 fix: update .gitignore to remove duplicate entry for storage 2026-03-24 08:28:37 +01:00
32bbc40979 Renamed Caddyfile template to a correct name. 2026-03-24 08:25:18 +01:00
58d0a96bfe update: change reverse proxy URLs to use localhost instead of account names 2026-03-24 08:24:41 +01:00
12b14460e9 refactor: remove cert-functions.sh as its functionality is no longer needed 2026-03-24 07:54:50 +01:00
c3c078fa7f fix: remove unnecessary echo statement when creating CA certificate and key 2026-03-24 07:43:19 +01:00
47efc09d18 fix: ensure CA certificate and key are created only if they do not exist 2026-03-24 07:42:37 +01:00
5a840f6577 update: ensure CA directory is created before generating certificates 2026-03-24 07:39:09 +01:00
8fdbce7fb4 update: make CA_DIR, CA_NAME, and STORAGE_ACCOUNT_NAME configurable 2026-03-24 07:38:36 +01:00
4f1d78f174 feat: add make-cert.sh script for generating self-signed CA and server certificates 2026-03-24 07:37:58 +01:00
12 changed files with 225 additions and 442 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,4 @@ storage
**/.terraform.lock.hcl
Caddyfile
*.env
ca
storage

View File

@@ -1,6 +1,4 @@
# Caddyfile for Azure Storage Emulator
# It uses /etc/hosts entries to route requests to the emulator
# Replace "__ACCOUNT_NAME__" with a desired storage account name if needed
__ACCOUNT_NAME__.blob.core.windows.net {
tls __AZURITE_STORAGE__/__ACCOUNT_NAME___cert.pem __AZURITE_STORAGE__/__ACCOUNT_NAME___key.pem
reverse_proxy https://__ACCOUNT_NAME__.blob.core.windows.net:10000 {

View File

@@ -26,9 +26,8 @@ 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 .
COPY ./Caddyfile.template .
RUN chmod +x entrypoint.sh
ENTRYPOINT [ "/app/entrypoint.sh" ]

137
README.md
View File

@@ -6,7 +6,7 @@ Review the [Documentation](#reference) for more details on how to use the emulat
## Installation
You can install and use the emulator in a few different ways, depending on your preferences and environment. The recommended way is to use a container runtime or Kubernetes, but you can also install it natively using Node.js.
You can install and use the emulator in a few different ways, depending on your preferences and environment. The recommended way is to use a container runtime or Kubernetes, but you can also install it natively using Node.js and Caddy HTTP Server.
### Using a container runtime
@@ -14,13 +14,29 @@ To run the Azure Storage Emulator in a container, follow these steps:
1. Ensure that a container runtime is installed. This repository supports both Docker and Apple `container` command.
2. Build the emulator image using the provided Dockerfile:
2. Create a custom non-public certificate for the emulator. Use the provided `make-cert.sh` script to generate a self-signed CA certificate and a server certificate for the specified storage account name. The script will use `CA_DIR` (default: `./storage`), `CA_NAME` (default: `Azurite Emulator CA`), and `STORAGE_ACCOUNT_NAME` (default: `azuritelocal`) environment variables to determine the storage location for the certificates, the name of the CA, and the storage account name for which the server certificate will be generated. For example:
```bash
./make-cert.sh
```
or
```bash
CA_DIR=./myca CA_NAME="My Custom CA" STORAGE_ACCOUNT_NAME=myaccount ./make-cert.sh
```
You can run the script multiple times with different `STORAGE_ACCOUNT_NAME` values to generate certificates for multiple storage accounts if needed. Just make sure to use the same `CA_DIR` and `CA_NAME` for all of them (or use the defaults) to ensure they are signed by the same CA.
3. Build the emulator image using the provided Dockerfile:
```bash
./build.sh
```
3. Run the emulator container:
or pull it from the Docker Hub registry using image name: `skoszewski/azurite:latest`.
4. Run the emulator container:
```bash
./start-azurite
@@ -28,36 +44,7 @@ To run the Azure Storage Emulator in a container, follow these steps:
You can also use the included example `compose.yaml` file for running it using `docker compose` (or any other compose compatible CLI).
### Using Kubernetes
Use the example manifest `azurite-deployment.yaml`.
1. Create the required Secret from your local env file:
```bash
kubectl create secret generic azurite-accounts \
--from-env-file="$HOME/.azurite/accounts.env"
```
2. Deploy the manifest:
```bash
kubectl apply -f azurite-deployment.yaml
```
3. Get the Kubernetes Service external IP:
```bash
kubectl get svc azurite -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
4. Add `/etc/hosts` entries on your workstation and point all Azurite account FQDNs to that Service IP (not `127.0.0.1`):
```
<service_ip> <accountname>.blob.core.windows.net <accountname>.queue.core.windows.net <accountname>.table.core.windows.net
```
### Using Node.js
### Using Node.js and Caddy HTTP Server
To install the Azure Storage Emulator natively on your machine, ensure you have Node.js (with npm) and Caddy HTTP Server installed, and follow these steps:
@@ -118,18 +105,14 @@ To install the Azure Storage Emulator natively on your machine, ensure you have
127.0.0.1 <accountname>.blob.core.windows.net <accountname>.queue.core.windows.net <accountname>.table.core.windows.net
```
8. Run the server:
8. Create a certificate for the specified account name using the `make-cert.sh` as described in the container runtime installation steps.
9. Run the server:
```bash
./run-server.sh
```
You can add the `--oauth` or `-o` flag to simulate OAuth authentication.
```bash
./run-server.sh --oauth
```
## Accessing the blob storage
### RClone
@@ -198,33 +181,6 @@ terraform {
## Command Reference
### `run-server.sh`
The script is the entry point for starting the Azure Storage Emulator natively. It discovers the account name and key from the `accounts.env` file, generates the necessary SSL certificates, configures Caddy for HTTPS endpoints, and starts the Azurite server with the appropriate settings.
It accepts the following optional flag: `--oauth` or `-o`. It enables OAuth simulation for the emulator. When this flag is set, the emulator simulates OAuth authentication flows, allowing you to test scenarios that involve Entra ID authentication. It does not implement a real authentication flow; instead, it accepts any valid token.
The script assumes that both Azurite and Caddy are installed and available in the system's PATH. It also assumes that the `accounts.env` file is properly configured with at least one account name and key, and that the `/etc/hosts` file contains the necessary entries for the custom domain names, e.g. `accountname.blob.core.windows.net`, `accountname.queue.core.windows.net`, and `accountname.table.core.windows.net`.
The storage location is determined by the `AZURITE_DIR` environment variable, which defaults to the relative `storage` subdirectory of the current directory if not set. Ensure that the specified directory is writable by the user running the script, as Azurite will need to create and manage files for the emulated storage accounts.
The script will generate self-signed SSL certificates for the specified account name and store them in the storage directory. Caddy will be configured to use these certificates for the HTTPS endpoints. The emulator will be accessible at the following endpoints:
- Blob service: `https://accountname.blob.core.windows.net`
- Queue service: `https://accountname.queue.core.windows.net`
- Table service: `https://accountname.table.core.windows.net`
You need to add `storage/ca_cert.pem` as a trusted root certificate in your system to avoid SSL errors when connecting to the emulator.
For Linux-based systems, you can use the following commands to add the certificate to the trusted store:
```bash
sudo cp storage/ca_cert.pem /usr/local/share/ca-certificates/azurite_ca_cert.crt
sudo update-ca-certificates
```
For macOS, you can use the Keychain Access application to import the certificate and mark it as trusted. Windows users can use the Certificate Manager to import the certificate into the "Trusted Root Certification Authorities" store.
### `build.sh`
The script builds a container image for the Azure Storage Emulator using the provided Dockerfile. The image includes the Azurite server and Caddy HTTP server, configured to run the emulator with triple HTTPS endpoints and optional OAuth simulation. It does not require Azurite or Caddy to be installed on the host machine, as they are included in the container image.
@@ -236,21 +192,48 @@ Accepted flags:
- `--latest`: Uses the latest released version of Azurite from GitHub as the base for the container image. This flag cannot be used together with `--version`.
- `--registry`: Specifies the container registry to which the built image will be pushed. If not provided, the image will only be built locally and not tagged with registry prefix.
Used environment variables:
- `AZURITE_IMAGE`: Overrides the default image name (`azurite:latest`) for the built container image. This can be useful if you want to use a different naming convention or push to a specific registry. The `--registry` flag will replace the registry part of the image name, but it will not override the entire name, so you can still use a custom image name with the registry prefix if needed.
### `start-azurite`
The script runs the Azure Storage Emulator using a supported container runtime (Docker or Apple `container` command). It accepts the same flag as `run-server.sh` to enable OAuth simulation (`--oauth` or `-o`). It also assumes `AZURITE_DIR` is either set in the environment or empty (not set), in which case it will default to the `./storage` subdirectory of the current directory. The script mounts the specified storage directory into the container, allowing you to persist data and access the generated SSL certificates on the host machine.
The default image name is `azurite:latest`, but it can be overridden by setting the `AZURITE_IMAGE` environment variable before running the script. For example:
```bash
AZURITE_IMAGE=myregistry/azurite:latest ./start-azurite
```
Both `AZURITE_DIR` and `AZURITE_IMAGE` should be set in the shell profile if you are running the emulator as a local Azure Storage replacement for development purposes.
The script runs the Azure Storage Emulator using a supported container runtime (Docker or Apple `container` command). It enables OAuth and starts Caddy by defualt.
> **Remember**: Make backups of the storage directory when the container is not running.
You have to use the same procedure as for `run-server.sh` to install the generated CA certificate as a trusted root certificate in your system to avoid SSL errors when connecting to the emulator.
Accepted flags:
- `--no-oauth` - disables OAuth simulation in the emulator. When this flag is set, you have to use the account key for authentication.
- `--no-caddy` - disables Caddy server and runs Azurite with its built-in HTTP server. This will result in the emulator being accessible over three HTTPS endpoints (ports: 10000, 10001, 10002). Use this flag if you want to run the emulator and proxy the endpoints yourself. Note that you have to configure your proxy to accept the certificate supplied for the emulator. Caddy uses `tls_trust_pool file <pem_cert_path>` directive.
Used environment variables:
- `AZURITE_DIR`: Specifies the directory on the host machine where the emulator will store its data. This directory will be mounted as `/storage` in the container, allowing the emulator to persist data across container restarts.
- `AZURITE_IMAGE`: Specifies the name of the container image to use when running the emulator. If not set, it defaults to `azurite:latest`, which is the default tag used by the `build.sh` script.
### `run-server.sh`
The script is the entry point for starting the Azure Storage Emulator natively. It discovers the account name and key from the `accounts.env` file, checks for the necessary SSL certificates, configures Caddy for HTTPS endpoints, and starts the Azurite server with the appropriate settings.
The script assumes that both Azurite and Caddy are installed and available in the system's PATH. It also assumes that the `accounts.env` file is properly configured with at least one account name and key, and that the `/etc/hosts` file contains the necessary entries mapped to `127.0.0.1` for the custom domain names, e.g. `accountname.blob.core.windows.net`, `accountname.queue.core.windows.net`, and `accountname.table.core.windows.net`.
The storage location is determined by the `AZURITE_DIR` environment variable. Data files are stored in the `storage` subdirectory. The directory structure pointed to by `AZURITE_DIR` will be created if it does not exist.
The script will use the certificate for the listed endpoints. Caddy will be configured to use that certificate for all HTTPS endpoints, therefore the certificate must have all the required SANs (Subject Alternative Names) for the endpoints and must be trusted by the system. The emulator will be accessible at the following endpoints:
- Blob service: `https://accountname.blob.core.windows.net`
- Queue service: `https://accountname.queue.core.windows.net`
- Table service: `https://accountname.table.core.windows.net`
For Debian-based systems, you can use the following commands to add the certificate to the trusted store:
```bash
sudo cp storage/ca_cert.pem /usr/local/share/ca-certificates/azurite_ca_cert.crt
sudo update-ca-certificates
```
For macOS, you can use the Keychain Access application to import the certificate and mark it as trusted. Windows users can use the Certificate Manager to import the certificate into the "Trusted Root Certification Authorities" store.
## Reference

View File

@@ -1,69 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: azurite-storage
labels:
app.kubernetes.io/name: azurite
app.kubernetes.io/component: storage-emulator
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: azurite
labels:
app.kubernetes.io/name: azurite
app.kubernetes.io/component: storage-emulator
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: azurite
template:
metadata:
labels:
app.kubernetes.io/name: azurite
app.kubernetes.io/component: storage-emulator
spec:
containers:
- name: azurite
image: azurite:latest
imagePullPolicy: IfNotPresent
args:
- --oauth
ports:
- name: https
containerPort: 443
protocol: TCP
envFrom:
- secretRef:
name: azurite-accounts
volumeMounts:
- name: storage
mountPath: /storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: azurite-storage
---
apiVersion: v1
kind: Service
metadata:
name: azurite
labels:
app.kubernetes.io/name: azurite
app.kubernetes.io/component: storage-emulator
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: azurite
ports:
- name: https
port: 443
targetPort: 443
protocol: TCP

View File

@@ -4,6 +4,7 @@ ARCH=()
VERSION_ARG=()
VERSION=""
REGISTRY=""
AZURITE_IMAGE="${AZURITE_IMAGE:-azurite:latest}"
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -33,18 +34,17 @@ while [[ $# -gt 0 ]]; do
esac
done
if [[ -z "$REGISTRY" ]]; then
IMAGE="azurite"
else
IMAGE="$REGISTRY/azurite"
if [[ ! -z "$REGISTRY" ]]; then
# Prepend the registry to the image name for tagging.
AZURITE_IMAGE="$REGISTRY/${AZURITE_IMAGE#*/}"
fi
if [[ -z "$VERSION" ]]; then
TAG_ARGS=("--tag" "$IMAGE:latest")
TAG_ARGS=("--tag" "${AZURITE_IMAGE%:*}:latest")
elif [[ "$VERSION" == "latest" ]]; then
TAG_ARGS=("--tag" "$IMAGE:${LATEST_TAG#v}" "--tag" "$IMAGE:latest")
TAG_ARGS=("--tag" "${AZURITE_IMAGE%:*}:${LATEST_TAG#v}" "--tag" "${AZURITE_IMAGE%:*}:latest")
else
TAG_ARGS=("--tag" "$IMAGE:${VERSION#v}")
TAG_ARGS=("--tag" "${AZURITE_IMAGE%:*}:${VERSION#v}")
fi
echo "Effective command line arguments:" ${ARCH[@]} ${VERSION_ARG[@]} ${TAG_ARGS[@]}

View File

@@ -1,176 +0,0 @@
function make_ca() {
# Use the provided directory argument or default to AZURITE_DIR if not provided
local CERT_DIR="$1"
local CA_NAME="$2"
if [[ -z "$CERT_DIR" || -z "$CA_NAME" || ! -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 '$CA_NAME' 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=${CA_NAME}" \
-text \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0"; then
echo "ERROR: Failed to generate CA certificate and key." >&2
return 1
fi
fi
return 0
}
function _is_ip() {
if [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
return 0
else
return 1
fi
}
function _is_dns() {
if [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; then
return 0
else
return 1
fi
}
function make_server_cert() {
local CERT_DIR="$1"
local CERT_SUBJECT_NAME="$2"
shift 2
if [[ -z "$CERT_DIR" || ! -d "$CERT_DIR" ]]; then
echo "ERROR: Certificate directory $CERT_DIR does not exist."
return 1
fi
if [[ -z "$CERT_SUBJECT_NAME" ]]; then
echo "ERROR: Subject name is required." >&2
return 1
fi
if ! _is_dns "$CERT_SUBJECT_NAME"; then
echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2
return 1
fi
if [[ ! -f "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then
echo "ERROR: CA certificate and key not found in $CERT_DIR. Please call make_ca first." >&2
return 1
fi
# Calculate the "account" name from the subject name, the hostname part before the first dot
local CERT_NAME="${CERT_SUBJECT_NAME%%.*}"
# Start with the subjectAltName extension containing the main DNS name
local SANS=("DNS:${CERT_SUBJECT_NAME}")
# Combine the remaining arguments into a single string for the subjectAltName extension
while [[ $# -gt 0 ]]; do
if _is_ip "$1"; then
SANS+=("IP:$1")
elif _is_dns "$1"; then
SANS+=("DNS:$1")
else
echo "ERROR: Invalid SAN entry '$1'" >&2
return 1
fi
shift
done
# Join the SAN entries with commas for the OpenSSL command
local SANS_EXT="subjectAltName=$(IFS=,; echo "${SANS[*]}")"
echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:"
for san in "${SANS[@]}"; do
echo " - $san"
done
# Generate server certificate and key if they don't exist
if [[ ! -f "$CERT_DIR/${CERT_NAME}_cert.pem" || ! -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then
echo "Generating server certificate and key..."
if ! openssl req \
-newkey rsa:4096 \
-keyout "$CERT_DIR/${CERT_NAME}_key.pem" \
-nodes \
-subj "/CN=${CERT_SUBJECT_NAME}" \
-addext "basicConstraints=critical,CA:FALSE" \
-addext "keyUsage=digitalSignature,keyEncipherment" \
-addext "extendedKeyUsage=serverAuth,clientAuth" \
-addext "$SANS_EXT" \
| 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/${CERT_NAME}_cert.pem"; then
echo "ERROR: Failed to generate server certificate and key." >&2
return 1
fi
fi
return 0
}
function make_pfx() {
local CERT_DIR="$1"
local CERT_NAME="$2"
local PFX_PASSWORD="${3:-}"
if [[ -z "$CERT_DIR" || ! -d "$CERT_DIR" ]]; then
echo "ERROR: Certificate directory $CERT_DIR does not exist."
return 1
fi
if [[ -z "$CERT_NAME" ]]; then
echo "ERROR: Certificate name is required." >&2
return 1
fi
if [[ ! -f "$CERT_DIR/${CERT_NAME}_cert.pem" || ! -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then
echo "ERROR: Server certificate and key not found in $CERT_DIR. Please call make_server_cert first." >&2
return 1
fi
if [[ ! -f "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then
echo "ERROR: CA certificate and key not found in $CERT_DIR. Please call make_ca first." >&2
return 1
fi
if [[ -z "$PFX_PASSWORD" ]]; then
PFX_PASSWORD="changeit"
fi
if [[ ! -f "$CERT_DIR/${CERT_NAME}.pfx" ]]; then
echo -n "Generating PKCS#12 (PFX) file..."
if ! openssl pkcs12 \
-export -out "$CERT_DIR/${CERT_NAME}.pfx" \
-inkey "$CERT_DIR/${CERT_NAME}_key.pem" \
-in "$CERT_DIR/${CERT_NAME}_cert.pem" \
-certfile "$CERT_DIR/ca_cert.pem" \
-password pass:"$PFX_PASSWORD"; then
echo "ERROR: Failed to generate PKCS#12 (PFX) file." >&2
return 1
fi
echo "done."
else
echo "PKCS#12 (PFX) file already exists, aborting generation."
return 1
fi
return 0
}

View File

@@ -2,8 +2,6 @@ services:
azurite:
image: azurite:latest
container_name: azurite
command:
- --oauth
ports:
- "443:443"
env_file:

View File

@@ -1,64 +1,29 @@
#!/bin/bash
set -e
# Include certificate generation functions.
. /app/cert-functions.sh
function make_account_cert() {
local CERT_DIR="$1"
local ACCOUNT_NAME="${2:-devstoreaccount1}"
# -addext "subjectAltName=DNS:${ACCOUNT_NAME}.blob.core.windows.net,DNS:${ACCOUNT_NAME}.queue.core.windows.net,DNS:${ACCOUNT_NAME}.table.core.windows.net,DNS:${ACCOUNT_NAME}.blob.localhost,DNS:${ACCOUNT_NAME}.queue.localhost,DNS:${ACCOUNT_NAME}.table.localhost,DNS:localhost,IP:127.0.0.1"
make_server_cert "$CERT_DIR" "${ACCOUNT_NAME}.blob.core.windows.net" \
"${ACCOUNT_NAME}.queue.core.windows.net" \
"${ACCOUNT_NAME}.table.core.windows.net" \
"${ACCOUNT_NAME}.blob.localhost" \
"${ACCOUNT_NAME}.queue.localhost" \
"${ACCOUNT_NAME}.table.localhost" \
"localhost" \
"127.0.0.1"
return $?
}
# Setup default storage location, account name and accounts file path.
AZURITE_STORAGE="${AZURITE_STORAGE:-/storage}"
set -euo pipefail
# Check, if the AZURITE_ACCOUNTS variable is set
if [[ -z "$AZURITE_ACCOUNTS" ]]; then
if [[ -f "$AZURITE_STORAGE/accounts.env" ]]; then
set -a
source "$AZURITE_STORAGE/accounts.env"
set +a
else
# Generate a default account
export AZURITE_ACCOUNTS="devstoreaccount1:$(openssl rand -base64 32)"
fi
echo "[ERROR] AZURITE_ACCOUNTS variable is not set." >&2
exit 1
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 -q "${ACCOUNT_NAME}" /etc/hosts; then
cp -a /etc/hosts /etc/hosts.bak && sed -E "/${ACCOUNT_NAME}/d" /etc/hosts > /etc/hosts
# Check, if the certificate for the account exists.
if [[ ! -f "/storage/${ACCOUNT_NAME}_cert.pem" ]] || [[ ! -f "/storage/${ACCOUNT_NAME}_key.pem" ]]; then
echo "[ERROR] Certificate or key for account ${ACCOUNT_NAME} not found."
exit 1
fi
cat <<EOF >> /etc/hosts
127.0.0.1 ${ACCOUNT_NAME}.blob.core.windows.net ${ACCOUNT_NAME}.queue.core.windows.net ${ACCOUNT_NAME}.table.core.windows.net ${ACCOUNT_NAME}.blob.localhost ${ACCOUNT_NAME}.queue.localhost ${ACCOUNT_NAME}.table.localhost
EOF
OAUTH_ARGS=()
NO_CADDY=
OAUTH_ARGS=("--oauth" "basic")
NO_CADDY=""
while [[ $# -gt 0 ]]; do
case "$1" in
--oauth)
OAUTH_ARGS=("--oauth" "basic")
# Ensure Caddy is disabled when using OAuth, as Azurite does not support OAuth behind a reverse proxy.
--no-oauth)
OAUTH_ARGS=()
shift
;;
--no-caddy)
@@ -72,25 +37,21 @@ while [[ $# -gt 0 ]]; do
esac
done
# Ensure certificates are generated before starting Azurite or Caddy.
if ! make_ca "$AZURITE_STORAGE" "Azurite CA $(date +%Y.%m)"; then
echo "Error: Failed to create CA certificate and key." >&2
exit 1
fi
if ! make_account_cert "$AZURITE_STORAGE" "$ACCOUNT_NAME"; then
echo "Error: Failed to create server certificate and key." >&2
exit 1
fi
if [[ -z "$NO_CADDY" ]]; then
# Generate a Caddyfile configuration based on the account name and storage directory.
sed -E "s/__ACCOUNT_NAME__/${ACCOUNT_NAME}/g; s|__AZURITE_STORAGE__|${AZURITE_STORAGE}|g" /app/Caddyfile.example > "$AZURITE_STORAGE/Caddyfile"
# Start Caddy in the background to handle HTTPS requests and route them to Azurite.
caddy start --config "$AZURITE_STORAGE/Caddyfile" # Use start not run, start does not block the shell process.
HOST_ARGS=("--blobHost" "127.0.0.1" "--queueHost" "127.0.0.1" "--tableHost" "127.0.0.1")
else
if [[ -n "$NO_CADDY" ]]; then
HOST_ARGS=("--blobHost" "0.0.0.0" "--queueHost" "0.0.0.0" "--tableHost" "0.0.0.0")
else
# Create /etc/hosts entries for the emulator endpoints, so that they can be accessed via their standard Azure Storage hostnames.
ALTNAMES=()
for name in blob queue table; do
ALTNAMES+=("${ACCOUNT_NAME}.${name}.core.windows.net")
done
echo "127.0.0.1 ${ALTNAMES[@]}" >> /etc/hosts
# Generate a Caddyfile configuration based on the account name and storage directory.
sed -E "s/__ACCOUNT_NAME__/${ACCOUNT_NAME}/g; s|__AZURITE_STORAGE__|/storage|g" /app/Caddyfile.template > "/storage/Caddyfile"
# Start Caddy in the background to handle HTTPS requests and route them to Azurite.
caddy start --config "/storage/Caddyfile" # Use start not run, start does not block the shell process.
HOST_ARGS=("--blobHost" "127.0.0.1" "--queueHost" "127.0.0.1" "--tableHost" "127.0.0.1")
fi
PORT_ARGS=("--blobPort" "10000" "--queuePort" "10001" "--tablePort" "10002")
@@ -98,6 +59,6 @@ PORT_ARGS=("--blobPort" "10000" "--queuePort" "10001" "--tablePort" "10002")
# Start Azurite with the appropriate arguments based on the configuration.
exec node /app/azurite/src/azurite.js \
--disableTelemetry \
--location "$AZURITE_STORAGE" \
--key "$AZURITE_STORAGE/${ACCOUNT_NAME}_key.pem" --cert "$AZURITE_STORAGE/${ACCOUNT_NAME}_cert.pem" \
--location "/storage" \
--key "/storage/${ACCOUNT_NAME}_key.pem" --cert "/storage/${ACCOUNT_NAME}_cert.pem" \
"${HOST_ARGS[@]}" "${PORT_ARGS[@]}" "${OAUTH_ARGS[@]}"

49
make-cert.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
#
# This script creates a self-signed CA and a server certificate for the Azurite Emulator.
#
# For more sophisticated certificate management, consider using Simple CA project
# from: https://gitea.koszewscy.waw.pl/slawek/simple-ca.git
CA_DIR="${CA_DIR:-./storage}"
CA_NAME="${CA_NAME:-Azurite Emulator CA}"
STORAGE_ACCOUNT_NAME="${STORAGE_ACCOUNT_NAME:-azuritelocal}"
mkdir -p "$CA_DIR"
if [[ ! -f "${CA_DIR}/ca_cert.pem" || ! -f "${CA_DIR}/ca_key.pem" ]]; then
openssl req \
-x509 -noenc -text \
-newkey rsa:4096 \
-keyout "${CA_DIR}/ca_key.pem" \
-out "${CA_DIR}/ca_cert.pem" \
-days 3650 \
-subj "/CN=$CA_NAME" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0"
HASH=$(openssl x509 -in "${CA_DIR}/ca_cert.pem" -noout -hash 2>/dev/null)
ln -sf ca_cert.pem "${CA_DIR}/$HASH.0"
fi
ALTNAMES=()
for endpoint in blob queue table; do
ALTNAMES+=("DNS:${STORAGE_ACCOUNT_NAME}.${endpoint}.core.windows.net")
done
openssl req \
-newkey rsa:4096 \
-noenc \
-keyout "${CA_DIR}/${STORAGE_ACCOUNT_NAME}_key.pem" \
-subj "/CN=${STORAGE_ACCOUNT_NAME}.blob.core.windows.net" \
-addext "basicConstraints=critical,CA:FALSE" \
-addext "keyUsage=digitalSignature, keyEncipherment" \
-addext "extendedKeyUsage=serverAuth" \
-addext "subjectAltName=$(IFS=, ; echo "${ALTNAMES[*]}")" \
| openssl x509 \
-req -text \
-CA "${CA_DIR}/ca_cert.pem" \
-CAkey "${CA_DIR}/ca_key.pem" \
-copy_extensions copyall \
-days 365 \
-out "${CA_DIR}/${STORAGE_ACCOUNT_NAME}_cert.pem"
openssl verify -CApath "${CA_DIR}" "${CA_DIR}/${STORAGE_ACCOUNT_NAME}_cert.pem"
cat <<EOF
Add the following line to your /etc/hosts file to resolve the emulator endpoints:
127.0.0.1 $(IFS=' '; echo "${ALTNAMES[@]#DNS:}")
EOF

View File

@@ -1,34 +1,36 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Include certificate generation functions.
. "$SCRIPT_DIR/cert-functions.sh"
AZURITE_DIR="${AZURITE_DIR:-$SCRIPT_DIR}"
AZURITE_DIR="$SCRIPT_DIR/storage"
if [[ ! -d "$AZURITE_DIR" ]]; then
echo "No accounts found"
exit 0
if ! mkdir -p "$AZURITE_DIR/storage"; then
echo "ERROR: Failed to create storage directory at $AZURITE_DIR/storage. Please ensure the path is correct and writable."
exit 1
fi
if [[ -z "$AZURITE_ACCOUNTS" && -f "$SCRIPT_DIR/accounts.env" ]]; then
if [[ -z "$AZURITE_ACCOUNTS" && -f "$AZURITE_DIR/accounts.env" ]]; then
set -a
. "$SCRIPT_DIR/accounts.env"
source "$AZURITE_DIR/accounts.env"
set +a
fi
if [[ -z "$AZURITE_ACCOUNTS" ]]; then
echo "[ERROR] AZURITE_ACCOUNTS variable is not set." >&2
exit 1
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
ALTNAMES=()
for name in blob queue table; do
if ! grep -qE "${ACCOUNT_NAME}.${name}.core.windows.net" /etc/hosts; then
echo "ERROR: /etc/hosts does not contain an entry for ${ACCOUNT_NAME}.${name}.core.windows.net mapping to 127.0.0.1."
exit 1
fi
done
if ! command -v azurite &> /dev/null; then
echo "ERROR: Azurite is not installed. Please install it with 'npm install -g azurite'"
@@ -40,13 +42,12 @@ if ! command -v caddy &> /dev/null; then
exit 1
fi
OAUTH_ARGS=()
OAUTH_ARGS=("--oauth" "basic")
while [[ $# -gt 0 ]]; do
case "$1" in
--oauth|-o)
OAUTH_ARGS=("--oauth" "basic")
# Ensure Caddy is disabled when using OAuth, as Azurite does not support OAuth behind a reverse proxy.
--no-oauth)
OAUTH_ARGS=()
shift
;;
*)
@@ -56,21 +57,16 @@ while [[ $# -gt 0 ]]; do
esac
done
# Ensure certificates are generated before starting Azurite or Caddy.
if ! make_ca "$AZURITE_DIR"; then
echo "Error: Failed to create CA certificate and key." >&2
if [[ ! -f "$AZURITE_DIR/storage/ca_cert.pem" ||
! -f "$AZURITE_DIR/storage/ca_key.pem" ||
! -f "$AZURITE_DIR/storage/${ACCOUNT_NAME}_cert.pem" ||
! -f "$AZURITE_DIR/storage/${ACCOUNT_NAME}_key.pem" ]]; then
echo "ERROR: SSL certificate or key files not found in $AZURITE_DIR/storage. Please run 'make-cert.sh' to create the necessary certificates and keys."
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
CERT_ARGS=()
# 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"
sed -E "s|__ACCOUNT_NAME__|${ACCOUNT_NAME}|g; s|__AZURITE_STORAGE__|${AZURITE_DIR}/storage|g" "$SCRIPT_DIR/Caddyfile.template" > "$AZURITE_DIR/Caddyfile"
echo "Starting Caddy server..."
caddy start --config "$AZURITE_DIR/Caddyfile" # Use start not run, start does not block the shell process.
@@ -80,7 +76,7 @@ trap "echo 'Stopping Caddy server...'; caddy stop" EXIT INT TERM HUP KILL STOP
echo "Starting Azurite..."
azurite \
--disableTelemetry \
--location "$AZURITE_DIR" \
--location "$AZURITE_DIR/storage" \
--blobHost 127.0.0.1 --queueHost 127.0.0.1 --tableHost 127.0.0.1 \
--key "$AZURITE_DIR/${ACCOUNT_NAME}_key.pem" --cert "$AZURITE_DIR/${ACCOUNT_NAME}_cert.pem" \
--key "$AZURITE_DIR/storage/${ACCOUNT_NAME}_key.pem" --cert "$AZURITE_DIR/storage/${ACCOUNT_NAME}_cert.pem" \
"${OAUTH_ARGS[@]}"

View File

@@ -1,14 +1,16 @@
#!/usr/bin/env bash
AZURITE_DIR="${AZURITE_DIR:-$(pwd)}"
set -euo pipefail
AZURITE_DIR="${AZURITE_DIR:-}"
CONTAINER_ARGS=()
AZURITE_IMAGE="${AZURITE_IMAGE:-azurite:latest}"
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--oauth)
# OAuth support
CONTAINER_ARGS+=("--oauth")
--no-oauth|--no-caddy)
# Pass these flags to the container, so that it can configure Azurite accordingly.
CONTAINER_ARGS+=("$1")
shift
;;
-d|--azurite-dir)
@@ -21,16 +23,58 @@ while [[ $# -gt 0 ]]; do
fi
;;
*)
echo "Unknown argument: $1" >&2
echo "Error: Unknown argument: $1" >&2
exit 1
;;
esac
done
# Check, if the AZURITE_DIR variable is set and is a valid directory
if [[ -z "$AZURITE_DIR" || ! -d "$AZURITE_DIR" ]]; then
echo "Error: AZURITE_DIR is not set or is not a valid directory." >&2
exit 1
fi
# Check, if the accounts.env file exists in the AZURITE_DIR directory
if [[ ! -f "$AZURITE_DIR/accounts.env" ]]; then
echo "Error: accounts.env file not found in AZURITE_DIR directory." >&2
exit 1
fi
source "$AZURITE_DIR/accounts.env"
if [[ -z "$AZURITE_ACCOUNTS" ]]; then
echo "Error: AZURITE_ACCOUNTS variable is not set in accounts.env file." >&2
exit 1
fi
ACCOUNT_NAME=$(echo "$AZURITE_ACCOUNTS" | cut -f 1 -d ';' | cut -f 1 -d ':')
# Check, if the certificate for the account exists.
if [[ ! -f "$AZURITE_DIR/storage/${ACCOUNT_NAME}_cert.pem" ]] || [[ ! -f "$AZURITE_DIR/storage/${ACCOUNT_NAME}_key.pem" ]]; then
echo "Error: Certificate or key for account ${ACCOUNT_NAME} not found in AZURITE_DIR directory." >&2
exit 1
fi
if command -v docker &> /dev/null; then
docker run --rm -d --name azurite --env-file "$AZURITE_DIR/accounts.env" -p 443:443 -v "$AZURITE_DIR/storage":/storage "$AZURITE_IMAGE" "${CONTAINER_ARGS[@]}"
docker run \
--rm \
-d \
--name azurite \
--env-file "$AZURITE_DIR/accounts.env" \
-p 443:443 \
-v "$AZURITE_DIR/storage":/storage \
"$AZURITE_IMAGE" "${CONTAINER_ARGS[@]}"
elif command -v container &> /dev/null; then
container run -c 2 -m 512M --rm -d --name azurite --env-file "$AZURITE_DIR/accounts.env" -p 443:443 --mount type=bind,source="$AZURITE_DIR/storage",target=/storage "$AZURITE_IMAGE" "${CONTAINER_ARGS[@]}"
container run \
-c 2 \
-m 512M \
--rm \
-d \
--name azurite \
--env-file "$AZURITE_DIR/accounts.env" \
-p 443:443 \
--mount type=bind,source="$AZURITE_DIR/storage",target=/storage \
"$AZURITE_IMAGE" "${CONTAINER_ARGS[@]}"
else
echo "Neither supported container runtime found." >&2
exit 1