Browser_use in the Ubuntu-Docker × Proxy × SSLインスペクション(SSL Bump)

最近Browser Useを利用する場面があり、Proxy+SSLインスペクション環境下において、CA関連でうまく動作せず四苦八苦したので一通り準備して検証してみました。OpenAIのAPIを利用しますので、事前にAPI_KEYを取得してください。

# 前提
OS：Ubuntu 22.04.5 LTS
Proxy:Squid Cache: Version 4.9（on Ubuntu18.04 LTS)
Docker version： 27.3.1, build ce12230
Docker Compose version： v2.29.7
llmモデル：gpt-4o
langchain: 0.3.14
langchain-openai: 0.3.1
openai: 1.61.1
playwright: 1.50.0
browser-use: 0.1.36

# 0. Browser_useとは？
下記サイトが詳しく書いてあったので、参考にさせていただきました。

https://zenn.dev/gunjo/articles/8450e69537dbb6


# 1. Docker関連ファイル作成/コンテナ起動
Dockerfile、RDP関連プロセスを動かす際に利用するsupervisord.conf、docker-compose.ymlを作成します。
```dockerfile:Dockerfile
FROM ubuntu:latest

RUN apt-get update && \
    DEBIAN_FRONTED=noninteractive apt-get install -y python3-pip \
    ubuntu-desktop xrdp xfce4 xfce4-goodies dbus-x11 x11-xserver-utils \
    supervisor software-properties-common

RUN echo "xfce4-session" >~/.xsession \
    && echo "exec startxfce4" >~/.xinitrc \
    && chmod +x ~/.xinitrc

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

EXPOSE 3389

CMD ["/usr/bin/supervisord"]
```

```vim:supervisord.conf
[supervisord]
nodaemon=true

[program:xrdp]
command=/usr/sbin/xrdp -nodaemon

[program:xrdp-sesman]
command=/usr/sbin/xrdp-sesman -nodaemon
```

```yaml:docker-compose.yml
services:
  rdp:
    image: ubuntu-rdp-desktop:0.1
    container_name: 'ubuntu-rdp-desktop'
    restart: always
    build:
      context: .
    tty: true
    ports:
      - "3389:3389"
    networks:
      - rdp_desktop_network

networks:
  rdp_desktop_network:
    driver: bridge
```

作成できたらコンテナを生成します。
```bash
docker compose up -d
```

以下コマンドで正しく起動しているか確認してください。
```bash
# docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED         STATUS         PORTS                                       NAMES
xxxxxxxxxxxx   ubuntu-rdp-desktop:0.1   "/usr/bin/supervisord"   3 seconds ago   Up 3 seconds   0.0.0.0:3389->3389/tcp, :::3389->3389/tcp   ubuntu-rdp-desktop

# ss -ant|grep 3389
LISTEN 0      4096         0.0.0.0:3389      0.0.0.0:*
LISTEN 0      4096            [::]:3389         [::]:*
```

# 2. Proxy/SSLインスペクション作業
ちょうど、自宅に18.04を動かしていたjetson nanoがあったのでこれを利用しました。手順は下記サイトを参考に実施させていただきました。

https://www7390uo.sakura.ne.jp/wordpress/archives/777

myCA.pemとして作成したファイルを転送しておきましょう。
```bash
# docker cp myCA.pem ubuntu-rdp-desktop:/tmp/
Successfully copied 4.61kB to ubuntu-rdp-desktop:/tmp/
```

SSLインスペクションについて:参考URL

https://www.janog.gr.jp/meeting/janog45/application/files/9515/7982/7303/JANOG45_Fortinet_SSL_pub1.pdf

# 3. パスワード変更/RDPログイン
rootでログインしますので、コンテナにログインしてrootのパスワードを変更します。

```bash
# docker exec -it ubuntu-rdp-desktop /bin/bash
root@xxxxxxxxxxxx:/# passwd
New password:
BAD PASSWORD: The password fails the dictionary check - it is too simplistic/systematic
Retype new password:
passwd: password updated successfully
root@xxxxxxxxxxxx:/#
```

リモートデスクトップツールでログインして、usernameにroot、変更したパスワードをpassword欄に入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/2244acb0-e2cc-7bda-52c4-269e4afe3106.png)

これでデスクトップ環境が表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/08cda243-d22a-8d7b-b387-c6caaf772ac9.png)

# 4. 追加パケージ、venv、証明書関連設定

まずはvenvを入れましょう。
```bash
root@xxxxxxxxxxxx:/# apt update
root@xxxxxxxxxxxx:/# apt install -y vim
root@xxxxxxxxxxxx:/# apt install -y python3-venv
```

ディレクトリを/rootに移動して作業し、venvで仮想環境を作成します。
```bash
root@xxxxxxxxxxxx:/# cd ~
root@xxxxxxxxxxxx:~# python3 -m venv browser_use
root@xxxxxxxxxxxx:~# source browser_use/bin/activate
(browser_use) root@xxxxxxxxxxxx:~#
```

続いて、証明書関連の設定を行います。事前にProxyサーバで生成したCAファイルを以下のようにコピーします。
```bash
(browser_use) root@xxxxxxxxxxxx:~# pip3 install certifi
(browser_use) root@xxxxxxxxxxxx:~# mkdir /usr/share/ca-certificates/myCA
(browser_use) root@xxxxxxxxxxxx:~# cp /tmp/myCA.pem /usr/share/ca-certificates/myCA/
```

ca-certificates.confに追記します。
```vim: /etc/ca-certificates.conf
mozilla/ACCVRAIZ1.crt
mozilla/AC_RAIZ_FNMT-RCM.crt
mozilla/AC_RAIZ_FNMT-RCM_SERVIDORES_SEGUROS.crt
～～～～省略～～～～
mozilla/vTrus_ECC_Root_CA.crt
mozilla/vTrus_Root_CA.crt
myCA/myCA.pem # これを追加
```

CAファイルの更新処理を実行します。
```bash
(browser_use) root@xxxxxxxxxxxx:~# update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
(browser_use) root@xxxxxxxxxxxx:~#
```

続いて、browser_useを動作させるため、必要となるpackageを入れます。
```bash
pip3 install langchain
pip3 install playwright
playwright install-deps
playwright install
pip3 install browser_use
```

# 5. 非Proxy構成でのテスト

Proxyを利用せず、直接通信させる構成でそもそもうまく動作するか確認します。OPENAIのキーを.envファイルに記載してください。

```vim:.env
OPENAI_API_KEY="sk-proj-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  # API_KEYを記入
```

browser_useを動かしてみます。私のサイトでいいねの数が多いトピックを５件引っ張ってきたいと思います。
```python:browser_test.py
import asyncio
from browser_use import Agent
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

load_dotenv()

async def main():
    agent = Agent(
        task="""
        Qiitaのnw-engineerサイトでいいねの数が多いトピックを5件表示してください。
        """,
        llm=ChatOpenAI(model="gpt-4o"),
    )
    result = await agent.run()
    print(result)

asyncio.run(main())
```

では、Desktop上で実行しましょう。
```bash
(browser_use) root@xxxxxxxxxxxx:~# python3 browser_test.py
```

#### 実行結果
うまく抽出できていますね。ちゃんと件数まで出ています。
```json
{
  "top_liked_topics": [
    {
      "title": "Pythonのセキュリティ関連ライブラリを試してみました。",
      "likes": 62
    },
    {
      "title": "React+FastAPI+OpenAIでヘルプレポートサイト作成",
      "likes": 39
    },
    {
      "title": "Fortigate VM（制限機能付き評価版）を利用するまでの流れ",
      "likes": 26
    },
    {
      "title": "Fortigate VMトライアルライセンス永続化",
      "likes": 22
    },
    {
      "title": "fastapi-usersでJWT認証 + MFAを使ったログイン実装",
      "likes": 21
    }
  ]
}
```

# 6. Proxy+SSLインスペクション構成
それでは、Proxy＋SSLインスペクション構成でテストします。まず転送しておいたmyCA.pemファイルを/root直下にcopyします。
```bash
mv /tmp/myCA.pem ./
```

続いてスクリプトを追記ます。
```python:browser_test.py
import asyncio
import certifi  ## 追加
import os       ## 追加
from browser_use import Agent
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

load_dotenv()

os.environ['REQUESTS_CA_BUNDLE'] = './myCA.pem'     ## 追加

os.environ['http_proxy'] = 'http://x.x.x.x:8080'  ## 追加
os.environ['https_proxy'] = 'http://x.x.x.x:8080' ## 追加
certifi.where = lambda: '/etc/ssl/certs/ca-certificates.crt'  ## 追加

async def main():
    agent = Agent(
        task="""
        Qiitaのnw-engineerサイトでいいねの数が多いトピックを5件表示してください。
        """,
        llm=ChatOpenAI(model="gpt-4o"),
    )
    result = await agent.run()
    print(result)

asyncio.run(main())

```


実行前にブラウザへCAファイルを読み込ませておきます。
Desktop上でplaywrightコマンドを実行します。

```bash
(browser_use) root@xxxxxxxxxxxx:~# playwright cr
```
画面右上の設定ボタンをクリックして、Settingsを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/9856566a-2d1b-4790-64a4-e6660fa9f5e5.png)

画面左側のPrivacy and Securityをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/f67cc105-cf14-ee8a-9087-7dad997c90e3.png)

画面中央を下にスクロールさせて、Securityを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/c2cd637e-5021-9cd6-a0b2-9ca6b50f7ebc.png)

さらに下にスクロールさせて、Manage certificates を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/85b659b4-599a-196a-981d-3ecbfffb0353.png)

Manage cetificatesのポップアップ中に表示されているAuthoritiesを選択して、importボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/3fbca2ba-a53d-c588-81e8-e14439507404.png)

/rootに配置したmyCA.pemを選択し、画面右下のSelectボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/83f0aec7-5bf3-28d5-572f-ea1f91399d76.png)

ポップアップのチェックボタンをすべてチェックして、OKボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/414ae2b7-2eaf-893f-478e-8012df6153a3.png)


新たにCA証明書(ここではorg-live-est)が表示されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/dda0c0f2-cb32-82db-c6ac-49393625bec7.png)

作成したスクリプト実行前に環境変数をセットして、playwrightを起動します。
```bash
(browser_use) root@xxxxxxxxxxxx:~# export https_proxy=http://x.x.x.x:8080
(browser_use) root@xxxxxxxxxxxx:~# export http_proxy=http://x.x.x.x:8080
(browser_use) root@xxxxxxxxxxxx:~# playwright cr
```

Google.co.jpにアクセスし、証明書を確認すると発行元（Issued By）がProxyのCAになっていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/2ba6692c-0652-7393-e46f-34f551298df1.png)


これでProxy＋SSLインスペクション環境下におけるbrowser_useの準備ができましたので、作成したスクリプトを再び、Desktop上で再実行します。
```bash
(browser_use) root@xxxxxxxxxxxx:~# python3 browser_test.py
```

正しく動作しています！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/f042b41d-6723-5bf2-1365-b8d443085fba.png)

NO_PROXYをos.environで定義すればローカルアドレスも操作できるのでかなり使い勝手がよさそうです。

# その他
ollamaを利用してローカルLLMも使えるようなのですが、できれば自前のAPIで利用したいとおもっているのでもう少しコードを解析したいと思います。