Heroku
[ip-lists-docs]: ../../user-guides/ip-lists/overview.md
[node-token-types]: ../../user-guides/nodes/nodes.md#api-and-node-tokens-for-node-creation
[ptrav-attack-docs]: ../../attacks-vulns-list.md#path-traversal
[attacks-in-ui-image]: ../../images/admin-guides/test-attacks-quickstart.png
[doc-stat-service]: ../../admin-en/configure-statistics-service.md
[aio-docs]: ../nginx/all-in-one.md
[waf-directives-instr]: ../../admin-en/configure-parameters-en.md
[filtration-mode-docs]: ../../admin-en/configure-wallarm-mode.md#available-filtration-modes
[api-spec-enforcement-docs]: ../../api-specification-enforcement/overview.md
# Heroku上でWallarmを実行
Wallarmは[Heroku](https://www.heroku.com/)クラウドプラットフォーム上にデプロイされたWebアプリケーションやAPIを保護します。本ガイドでは、WallarmノードをHeroku上で実行してリアルタイムにトラフィックを解析する手順を説明します。
現時点では、WallarmからHeroku向けの公式Dockerイメージは存在しません。本ガイドでは、[オールインワンインストーラー][aio-docs]を使用して自身でDockerイメージを作成し実行する方法を解説します。
## 必要条件
* ホストシステムに[Docker](https://docs.docker.com/engine/install/)がインストールされていること
* Heroku用のWallarm DockerイメージをプッシュするためのDockerアカウント
* ホストシステムに[Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli)がインストールされていること
* HerokuのWebダイノ上で実行されるアプリケーションまたはAPI
* [US Cloud](https://us1.my.wallarm.com/)または[EU Cloud](https://my.wallarm.com/)のWallarm Consoleへの管理者アクセス
* オールインワンWallarmインストーラーをダウンロードするための`https://meganode.wallarm.com`へのアクセス
* US Wallarm Cloudを利用する場合は`https://us1.api.wallarm.com`、EU Wallarm Cloudを利用する場合は`https://api.wallarm.com`へのアクセス
* 以下のIPアドレスへのアクセス(攻撃検知ルールの更新および[API仕様書][api-spec-enforcement-docs]の取得、また[allowlisted, denylisted, graylisted][ip-lists-docs]の国、地域、またはデータセンターの正確なIPを取得するため)
=== "US Cloud"
```
34.96.64.17
34.110.183.149
35.235.66.155
34.102.90.100
34.94.156.115
35.235.115.105
```
=== "EU Cloud"
```
34.160.38.183
34.144.227.90
34.90.110.226
```
## Step 1: Wallarm Docker設定の準備
Heroku上にWallarmのDockerイメージをデプロイするため、まずイメージビルドプロセス用の必要な設定ファイルを作成します。以下の手順に従ってください。
1. ローカルシステム上にWallarmのDocker設定専用のディレクトリを作成し、そのディレクトリに移動します。
1. NGINXと[Wallarmの設定][waf-directives-instr]を含む`nginx.conf`ファイルを作成します。Dockerイメージは[NGINX互換のオールインワンインストーラー][aio-docs]をベースにしているため、NGINXが適切に構成されていることを確認してください。
以下は、Wallarmノードをmonitoringモードで実行する基本設定のテンプレートです。
```
daemon off;
worker_processes auto;
load_module /opt/wallarm/modules/bullseye-1180/ngx_http_wallarm_module.so;
pid /tmp/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
use epoll;
accept_mutex on;
}
http {
gzip on;
gzip_comp_level 2;
gzip_min_length 512;
gzip_proxied any; # Heroku router sends Via header
proxy_temp_path /tmp/proxy_temp;
client_body_temp_path /tmp/client_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Main Heroku app
server {
listen $PORT default_server;
server_name _;
wallarm_mode monitoring;
location / {
proxy_pass http://unix:/tmp/nginx.socket;
# Heroku apps are always behind a load balancer, which is why we trust all IPs
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive off;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header "Connection" "";
}
error_page 403 /403.html;
location = /403.html {
root /usr/share/nginx/html;
internal;
}
}
# Wallarm status helper (localhost-only)
server {
listen 127.0.0.8:$PORT;
server_name localhost;
allow 127.0.0.0/8;
deny all;
wallarm_mode off;
disable_acl "on";
access_log off;
location ~/wallarm-status$ {
wallarm_status on;
}
}
}
```
1. Wallarm Dockerイメージ用の指示が記載された`entrypoint.sh`ファイルを作成します。
```
#!/bin/bash
set -e
log() {
local msg="$1"
local level="$2"
if [ -z "$level" ]; then
level="INFO"
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg"
}
log "Script execution started."
# Ensure necessary directories exist for supervisord.
log "Ensuring necessary directories exist for supervisord."
mkdir -p /opt/wallarm/var/log/wallarm
mkdir -p /opt/wallarm/run/supervisor
if [ ! -z "$WALLARM_API_TOKEN" ]; then
log "WALLARM_API_TOKEN is set, checking configuration."
if [[ $DYNO == web.* ]]; then
log "Heroku dyno type [$DYNO] is 'web', proceeding with Wallarm configuration."
# Propagate env vars
log "Propagating environment variables from /opt/wallarm/env.list."
set -a
source /opt/wallarm/env.list
if [ -s /etc/wallarm-override/env.list ]; then
log "Propagating environment variables from /etc/wallarm-override/env.list."
source /etc/wallarm-override/env.list
fi
set +a
# Register Wallarm node in the Cloud.
log "Registering Wallarm node in the cloud."
/opt/wallarm/register-node
# Configure PORT in nginx config.
log "Replacing \$PORT in Nginx configuration with value $PORT."
sed -i "s/\$PORT/${PORT}/g" /etc/nginx/nginx.conf
# Verify that PORT was replaced successfully.
log "Checking if the PORT in Nginx configuration was successfully replaced."
if cat /etc/nginx/nginx.conf | grep -q "listen ${PORT}"; then
log "Successfully replaced PORT in Nginx configuration with value $PORT."
else
log "Failed to replace PORT in Nginx configuration!" "ERROR"
exit 1
fi
# Export $PORT as $NGINX_PORT (required for the `export-metrics` script).
log "Exporting PORT as NGINX_PORT for Wallarm metrics."
export NGINX_PORT="$PORT"
export -n TT_MEMTX_MEMORY
if [ ! -z "$NGINX_PORT" ]; then
sed -i -r "s#http://127.0.0.8/wallarm-status#http://127.0.0.8:$NGINX_PORT/wallarm-status#" \
/opt/wallarm/etc/collectd/wallarm-collectd.conf.d/nginx-wallarm.conf
fi
# Start all Wallarm services and NGINX under supervisord.
log "Starting all Wallarm services and NGINX under supervisord."
/opt/wallarm/usr/bin/python3.10 /opt/wallarm/usr/bin/supervisord -c /opt/wallarm/etc/supervisord.conf --loglevel=debug
# Check if supervisord started successfully.
log "Checking if supervisord process is running."
if pgrep -f "supervisord" > /dev/null; then
log "supervisord process started successfully."
else
log "Failed to start supervisord process!" "ERROR"
exit 1
fi
# Check the status of services managed by supervisord.
log "Checking the status of all services managed by supervisord every 10s during 3 minutes."
timeout=0
while (/opt/wallarm/usr/bin/supervisorctl -c /opt/wallarm/etc/supervisord.conf status | grep -qv "RUNNING");
do
log "One or more services failed to start!" "ERROR"
log "Waiting 10s and check it again"
sleep 10s
timeout=$(( timeout + 10 ))
if [ $timeout -ge 180 ];
then
log "One or more services failed to start!" "ERROR"
/opt/wallarm/usr/bin/supervisorctl -c /opt/wallarm/etc/supervisord.conf status
exit 1
fi
done
log "All services are running successfully."
log "Wallarm configuration completed."
else
log "Heroku dyno type [$DYNO] is not 'web', skipping Wallarm configuration."
fi
else
log "WALLARM_API_TOKEN is not set, executing CMD."
fi
# Execute the CMD command.
log "Executing command: $@"
exec "$@"
log "Script execution finished."
```
1. 次のコマンドを実行して、`entrypoint.sh`ファイルのパーミッションを`-rwxr-xr-x`に設定します。
```
chmod 755 entrypoint.sh
```
1. Wallarmがリクエストをブロックした際に表示する、見やすいエラーページを表示する`403.html`ファイルを作成します。以下の内容をコピーしてください。
```html
<!doctype html> <html> <head> <meta charset=utf-8> <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name=viewport> <title>Forbidden</title> <link rel="shortcut icon" type="image/x-icon" href="https://www.herokucdn.com/favicon.ico"> <style>html, body {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
background-color: #F7F8FB;
height: 100%;
-webkit-font-smoothing: antialiased;
}
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.message {
text-align: center;
align-self: center;
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
max-width: 450px;
}
.message__title {
font-size: 22px;
font-weight: 100;
margin-top: 15px;
color: #47494E;
margin-bottom: 8px;
}
p {
-webkit-margin-after: 0px;
-webkit-margin-before: 0px;
font-size: 15px;
color: #7F828B;
line-height: 21px;
margin-bottom: 4px;
}
.btn {
text-decoration: none;
padding: 8px 15px;
border-radius: 4px;
margin-top: 10px;
font-size: 14px;
color: #7F828B;
border: 1px solid #7F828B;
}
.hk-logo, .app-icon {
fill: #DBE1EC;
}
.info {
fill: #9FABBC;
}
body.friendly {
background: -webkit-linear-gradient(-45deg, #8363a1 0%, #74a8c3 100%);
background: linear-gradient(135deg, #8363a1 0%, #74a8c3 100%);
}
body.friendly .message__title {
color: #fff;
}
body.friendly p {
color: rgba(255, 255, 255, 0.6);
}
body.friendly .hk-logo, body.friendly .app-icon {
fill: rgba(255, 255, 255, 0.9);
}
body.friendly .info {
fill: rgba(255, 255, 255, 0.9);
}
body.friendly .btn {
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.9);
}
.info_area {
position: fixed;
right: 12px;
bottom: 12px;
}
.logo {
position: fixed;
left: 12px;
bottom: 12px;
}
</style> <base target=_parent /> </head> <body> <div class=spacer></div> <div class=message> <svg width=49 height=51 xmlns="http://www.w3.org/2000/svg"><path d="M3.9468 10.0288L20.5548.995c2.4433-1.3267 5.45-1.3267 7.8936 0l16.6078 9.0338C47.4966 11.3585 49 13.8102 49 16.4666V34.534c0 2.6537-1.5034 5.1082-3.9438 6.438l-16.6078 9.0307c-2.4435 1.3297-5.4503 1.3297-7.8937 0L3.9467 40.972C1.5035 39.642 0 37.1876 0 34.534V16.4667c0-2.6564 1.5034-5.108 3.9468-6.4378z" class=app-icon fill-rule=evenodd /></svg> <div class=message__title> Your request has been blocked </div> <p> If you think this is a mistake, please get in touch with the app's support team. </p> </div> <div class=logo> <svg width=85 height=24 xmlns="http://www.w3.org/2000/svg"><g class=info fill-rule=evenodd><path d="M27.8866 16.836h2.373v-3.504h2.919v3.504h2.373V8.164h-2.373v3.2227h-2.919V8.164h-2.373v8.672zm10.4888 0h6.4666V14.949h-4.0935v-1.6054h2.7764v-1.8282h-2.7765v-1.4062h3.8918V8.164h-6.265v8.672zm8.8396 0h2.3256V13.824h.6526L51.89 16.836h2.5154l-1.863-3.3165c1.151-.3867 1.7325-1.1718 1.7325-2.5312 0-2.086-1.3765-2.8242-3.631-2.8242h-3.429v8.672zm2.3256-4.793v-1.9805h1.0204c.973 0 1.4.2578 1.4.9844 0 .7264-.427.996-1.4.996h-1.0204zM60.8363 17c2.112 0 4.307-1.3242 4.307-4.5 0-3.1758-2.195-4.5-4.307-4.5-2.124 0-4.319 1.3242-4.319 4.5 0 3.1758 2.195 4.5 4.319 4.5zm0-1.875c-1.2458 0-1.946-1.0313-1.946-2.625 0-1.5938.7002-2.5664 1.946-2.5664 1.234 0 1.934.9726 1.934 2.5664 0 1.5938-.7 2.625-1.934 2.625zm6.7157 1.711h2.373v-2.6954l.6764-.7734 2.0764 3.4687h2.6816l-3.2155-5.25 2.9543-3.422h-2.7527l-2.4205 3.1407V8.164h-2.373v8.672zm13.4552.1288c2.563 0 3.6782-1.3125 3.6782-3.6093V8.164H82.36v5.1798c0 1.1953-.3798 1.7343-1.329 1.7343-.9493 0-1.3408-.539-1.3408-1.7342V8.164h-2.373v5.1915c0 2.2968 1.127 3.6093 3.69 3.6093zM2.4444 0C.9214 0 0 .8883 0 2.3226v19.3548C0 23.1068.9215 24 2.4444 24h17.1112C21.0736 24 22 23.1117 22 21.6774V2.3226C21.995.8883 21.0735 0 19.5556 0H2.4444zm16.8973 1.9c.4025.0045.7583.3483.7583.7214v18.7572c0 .3776-.3558.7214-.7583.7214H2.6583c-.4025 0-.7583-.3438-.7583-.7214V2.6214c0-.3777.3558-.7214.7583-.7214h16.6834z"/><path d="M16.43 20h-2.2527v-6.8048c0-.619-.1917-.838-.3786-.9666-1.131-.7667-4.3855-.0334-6.3458.7333l-1.553.6475L5.9048 4h2.2814v6.3333c.4314-.1333.973-.2714 1.524-.3857 2.4206-.5143 4.1987-.3762 5.3586.4048.6375.4286 1.3612 1.2714 1.3612 2.8428V20zM11.57 8h2.6675c1.4042-1.75 1.9732-3.35 2.1925-4h-2.6623c-.3967.95-1.1223 2.55-2.1977 4zM5.9 20v-5.6l2.43 2.8L5.9 20z"/></g></svg> </div> </body> </html>
```
1. WallarmのDockerイメージのビルドプロセスを記述する`Dockerfile`を作成します。
```dockerfile
FROM ubuntu:22.04
ARG VERSION="5.0.2"
ENV PORT=3000
ENV WALLARM_LABELS="group=heroku"
ENV WALLARM_API_TOKEN=
ENV WALLARM_API_HOST="us1.api.wallarm.com"
ENV TT_MEMTX_MEMORY=268435456
RUN apt-get -qqy update && apt-get -qqy install nginx curl && apt-get clean
# Download and unpack the Wallarm all-in-one installer
RUN curl -o /install.sh "https://meganode.wallarm.com/$(echo "$VERSION" | cut -d '.' -f 1-2)/wallarm-$VERSION.x86_64-glibc.sh" \
&& chmod +x /install.sh \
&& /install.sh --noexec --target /opt/wallarm \
&& rm -f /install.sh
# Set Tarantool's $PORT variable explicitly as it conflicts with Heroku's $PORT
RUN sed -i '/^\[program:tarantool\]$/a environment=PORT=3313' /opt/wallarm/etc/supervisord.conf
# Run supervisord in background. Our foreground process is the Heroku app itself
RUN sed -i '/nodaemon=true/d' /opt/wallarm/etc/supervisord.conf
# Add NGINX to supervisord
RUN printf "\n\n[program:nginx]\ncommand=/usr/sbin/nginx\nautorestart=true\nstartretries=4294967295\n" | tee -a /opt/wallarm/etc/supervisord.conf
# Heroku runs everything under an unprivileged user (dyno:dyno), so we need to grant it access to Wallarm directories
RUN find /opt/wallarm -type d -exec chmod 777 {} \;
# Copy NGINX configuration
COPY nginx.conf /etc/nginx/nginx.conf
# Herokuesque 403 error page
COPY 403.html /usr/share/nginx/html/403.html
# Add entrypoint.sh
COPY entrypoint.sh /entrypoint.sh
# Let entrypoint modify the config under dyno:dyno and redirect NGINX logs to console
RUN chmod 666 /etc/nginx/nginx.conf \
&& chmod 777 /etc/nginx/ \
&& ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
ENTRYPOINT ["/entrypoint.sh"]
```
## Step 2: Heroku用のWallarm Dockerイメージをビルド
前に作成したディレクトリ内で、以下のコマンドを実行してください。
docker build -t wallarm-heroku:5.0.2 .
docker login
docker tag wallarm-heroku:5.0.2
docker push
## Step 3: Heroku上でビルド済みDockerイメージを実行
イメージをHerokuにデプロイするには、以下の手順に従ってください。
1. 次の操作を行うため、アプリケーションディレクトリのルートに移動します。
1. アプリケーションのランタイムに固有な必要な依存関係をインストールする`Dockerfile`を作成します。Node.jsアプリケーションの場合、以下のテンプレートを使用してください。
```dockerfile
FROM <DOCKERHUB_USERNAME>/wallarm-heroku:5.0.2
ENV NODE_MAJOR=20
# Install NodeJS v20 from NodeSource
RUN apt-get update \
&& apt-get install -qqy ca-certificates curl gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install nodejs -qqy \
&& apt-get clean
ADD . /opt/webapp
WORKDIR /opt/webapp
# Install dependencies and build the app
RUN npm install --omit=dev
ENV npm_config_prefix /opt/webapp
# Note that in private spaces the `run` section of heroku.yml is ignored
# See: https://devcenter.heroku.com/articles/build-docker-images-heroku-yml#known-issues-and-limitations
CMD ["npm", "run", "start"]
```
1. 以下の内容で`heroku.yml`構成ファイルを作成します。
```yaml
build:
docker:
web: Dockerfile
```
1. アプリケーションが`$PORT`ではなく`/tmp/nginx.socket`でリッスンするように調整してください。なぜなら、NGINXが`$PORT`を使用しているためです。例えば、以下のような設定になります。
```js hl_lines="4-5"
// app.js
const app = require('express')()
let port = process.env.PORT || 3000 // If Wallarm is not configured, listen on $PORT
if(process.env.WALLARM_API_TOKEN) port = '/tmp/nginx.socket' // Wallarm is configured
app.listen(port, (err) => {
if (err) throw err
console.log(`> App is listening on ${port}`)
})
app.get('/', (req, res) => {
res.send('This app is protected by Wallarm')
})
```
1. WallarmノードインスタンスをWallarm Cloudにリンクするために、[適切なタイプ][node-token-types]のフィルタリングノードトークンを生成してください。
=== "API token"
1. Wallarm Console → **Settings** → **API tokens** へ移動し、[US Cloud](https://us1.my.wallarm.com/settings/api-tokens)または[EU Cloud](https://my.wallarm.com/settings/api-tokens)から操作します。
1. `Deploy`ソースロールのAPIトークンを見つけるか作成します。
1. このトークンをコピーします。
1. Wallarmノードを追加するノードグループ名を、次の環境変数で指定してください。
```
heroku config:set WALLARM_LABELS=group=<NODE_GROUP_NAME>
```
=== "Node token"
1. Wallarm Console → **Nodes** に移動し、[US Cloud](https://us1.my.wallarm.com/nodes)または[EU Cloud](https://my.wallarm.com/nodes)から操作します。
1. **Wallarm node**タイプのフィルタリングノードを作成し、生成されたトークンをコピーします。
1. ノードをCloudに接続するためのパラメータを、関連する環境変数で設定します。
=== "US Cloud"
```
heroku config:set WALLARM_API_TOKEN=<NODE_TOKEN>
```
=== "EU Cloud"
```
heroku config:set WALLARM_API_HOST=api.wallarm.com
heroku config:set WALLARM_API_TOKEN=<NODE_TOKEN>
```
1. アプリケーションをプッシュして再起動をトリガーし、Wallarmノードをデプロイしてください。
```
git add Dockerfile heroku.yml app.js
git commit -m "Add Wallarm Docker"
heroku stack:set container
git push heroku <BRANCH_NAME>
```
## Step 4: デプロイのテスト
デプロイが正常に機能することを確認するため、[Path Traversal][ptrav-attack-docs]脆弱性を利用したテスト攻撃を実行してください。
curl http://
ノードはデフォルトで**monitoring**の[filtration mode][filtration-mode-docs]で動作するため、Wallarmノードは攻撃をブロックせずに記録します。攻撃が記録されたかを確認するには、Wallarm Console → **Attacks**に進んでください。
![Attacks in the interface][attacks-in-ui-image]
## Debug
WallarmベースのDockerイメージに問題が発生した場合、Herokuのログを確認してエラーメッセージを特定してください。
heroku logs --tail