Storage Browser for Amazon S3 を動かしてみた

:::note info
この記事は [デジタルキューブ ＆ ヘプタゴン Advent Calendar 2024](https://qiita.com/advent-calendar/2024/digitalcube-heptagon) の12月23日分の記事です
:::

ヘプタゴンでクラウドアーキテクトをしている札幌在住の「みうみう」こと三浦一樹です。札幌はもうしっかり雪も積もり路面もツルツルです。転倒して骨折とかしちゃわないように注意しないと。。。

## Share your lessons

さて、AWS re:Invent が今年もラスベガスで開催されました。私は[2023年から AWS Community Hero の認定](https://aws.amazon.com/jp/developer/community/heroes/kazuki-miura/)をいただいており、そのベネフィットのひとつとして、AWS re:Invent に招待していただきました。現地で様々な体験をしましたが、一番印象に残っているのは地時間木曜日の朝一。個人的に一番の目的であるWerner 先生の Keynote です。この中で「Share your lessons」という言葉を使って、ひとりひとりへのアウトプットを促していました。この言葉を胸に刻み、各地のJAWS-UGが開催している re:Invent の様々な re:Cap イベントであったり、このブログであったりを通して、私の得られた知見を皆さんにシェアしていければと思います。ぜひ、みなさんもどんな形でも些細なことでも構いません。なんかしらの「Share your lessons」を続けてみてはいかがでしょうか。

![shareyourlessons.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/7b3cfcc1-99cf-8870-9587-b4a645040d7d.jpeg)

## Storage Browser for Amazon S3 とは

さて、そんな re:Invent のギリギリ前に、GAを迎えたのが、今回のテーマである「Storage Browser for Amazon S3」です。これは、AWS コンソールから触れる新機能ではなく、[Amplify UI という AWS が提供しているオープンソースのUIライブラリ](https://github.com/aws-amplify/amplify-ui)の一部としてAmazon S3 を操作できるものが加わった。というリリースになります。Amplify UI は ReactやVue、Angular など主要なフロントエンドのフレームワークには対応していますが、もっとも機能が充実しているのは React です
もともと[2024年9月5日にアルファリリース](https://aws.amazon.com/about-aws/whats-new/2024/09/storage-browser-amazon-s3-alpha-release/)として発表されていたものが、AWS re:Invent 2024 の期間中である [2024年12月1日にGA](https://aws.amazon.com/about-aws/whats-new/2024/12/storage-browser-amazon-s3/)したという経緯になります。今回GAされたものもReact用のUIについてです。

Amplify UI のドキュメントにおける Storage Browser for Amazon S3 の箇所を見て、どのような特徴があるのかを調べてみましょう。

https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser

> End users work with S3 locations within the Storage Browser interface. Locations are S3 buckets or prefixes that you authorize end users to access using Amazon S3 Access Grants or Identity and Access Management (IAM) policies, depending on your use case. When the Storage Browser component is first rendered, it will show the LocationsView which displays only the locations you have granted them access to. Once an end user has selected a location, they can browse the S3 bucket or prefix, and all the data contained further down the S3 resource path, but they cannot browse buckets or prefixes higher up the S3 resource path.

Amazon S3 Access GrantsやIAMポリシーに準拠する形で、指定したS3 バケットやその配下のプレフィックスの部分を開くことができる画面を作ることができるようです。

FTPクライアントなどでS3のデータを操作する場合に比べると、より細かい制限を効かせた状態を実現することが可能のようです。

## 認証方法

Storage Browser for Amazon S3を使用するための認証方法は以下の３つが用意されています

1. Amplify auth
    - Amazon Cognito と IAM ポリシーを使った制御
    - 個々のユーザーが自身のデータにアクセスする場合に最適
2. AWS IAM Identity Center and S3 Access Grants
    - IAM Identity Center 経由で全てのログが AWS CloudTrail に残る
    - 企業内で従業員が組織のデータにアクセスする場合に最適
3. Customer managed auth
    - すでに持っている認証認可のサービスを使用るすことも可能
    - 表示させたいS3のロケーションに対応する形でSTSのトークンを都度fetchする仕組みを自分で用意する必要がある

今回は１番の「Amplify Auth」を使用する形で、S3のデータをStorage Browser for Amazon S3経由で触るところまで触ってみたいと思います。

## 下ごしらえ。

Storage Browser for Amazon S3を動かすための、ログイン機能を持ったサンプルアプリケーションをAmplify のクイックスタートのドキュメントに従って作成していきます。

### ベースとなるサンプルアプリを Amplify にデプロイ

まずは、Amplify Gen 2 に React と vite で作られたテンプレートを元にデプロイまでしてみます。

https://docs.amplify.aws/react/start/quickstart/

クイックスタートのページにある「Create repository from template」から、GitHubのリポジトリを自分の環境に作成します。作成したGitHubリポジトリが見えない場合は「GitHubのアクセス許可をアップデート」から追加することができます。

今回作成したリポジトリを指定し、その他はデフォルトのままで、Amplify 上でデプロイを行います。

私の環境では、6分40秒でサンプルアプリケーションがデプロイされました。

![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/d0db8cdd-21c9-609e-6065-a93441216cf6.png)

ドキュメントには Data や Data Manager に関しての記述がありますが、今回は使わないので、一旦放置で大丈夫です。

### ローカルの環境で確認

https://docs.amplify.aws/react/start/quickstart/#4-set-up-local-environment

ローカルでReactが動くところまで持っていきましょう。VS Code などでリポジトリをクローンしてローカルで開きます。

Node.js と npm と vite が前提としては必要です

```shell
$ node -v
v18.20.5
```
```shell
$ npm -v
10.8.2
```
```shell
$ vite -v
vite/6.0.3 darwin-arm64 node-v18.20.5
```

ドキュメントを参考にして初回のビルドの部分から、バックエンドに関する情報が入っている amplify_outputs.json をAmplifyコンソールからダウンロードしてルートに配置します。

```shell
$ npm install
```
package.json で定義されているパッケージをインストールしたら
```shell
$ vite
```
これにて localhost での動きを確認できるようになります

### ログイン機能の追加

https://docs.amplify.aws/react/start/quickstart/#6-implement-login-ui
**6.Implement login UI** の部分を進めます。
このサンプルアプリケーションでは、すでに、`amplify/auth/resource.ts` にてCognito に関する定義がされているため、ログイン画面を簡単に追加することができます。

`src/main.tsx` と `src/App.tsx` の２つのファイルをそれぞれドキュメントにしたがって変更してください。

```diff_tsx:src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
+ import { Authenticator } from '@aws-amplify/ui-react';
import App from "./App.tsx";
import "./index.css";
import { Amplify } from "aws-amplify";
import outputs from "../amplify_outputs.json";
+ import '@aws-amplify/ui-react/styles.css';

Amplify.configure(outputs);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
+    <Authenticator>
      <App />
+    </Authenticator>
  </React.StrictMode>
);
```

```diff_tsx:src/App.tsx
import { useEffect, useState } from "react";
import type { Schema } from "../amplify/data/resource";
+ import { useAuthenticator } from '@aws-amplify/ui-react';
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

function App() {
+ const { signOut } = useAuthenticator();
  const [todos, setTodos] = useState<Array<Schema["Todo"]["type"]>>([]);

  useEffect(() => {
    client.models.Todo.observeQuery().subscribe({
      next: (data) => setTodos([...data.items]),
    });
  }, []);

  function createTodo() {
    client.models.Todo.create({ content: window.prompt("Todo content") });
  }

  return (
    <main>
+     <button onClick={signOut}>Sign out</button>
      <h1>My todos</h1>
      <button onClick={createTodo}>+ new</button>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.content}</li>
        ))}
      </ul>
      <div>
        🥳 App successfully hosted. Try creating a new todo.
        <br />
        <a href="https://docs.amplify.aws/react/start/quickstart/#make-frontend-updates">
          Review next step of this tutorial.
        </a>
      </div>
    </main>
  );
}

export default App;

```

**7. Set up local AWS credentials** と **8. Deploy cloud sandbox** はAmplify Gen 2 の特徴的な機能なのですが、今回はスキップします。サンドボックス環境はとても便利なので、ぜひ試していただきたいので、ぜひまたの機会に。

**9. Implement per-user authorization** の `src/App.tsx` に関しての変更だけを加えてみます。

```diff_tsx:src/App.tsx
// ... imports


function App() {
-  const { signOut } = useAuthenticator();
+  const { user, signOut } = useAuthenticator();

  // ...
  
  }

  return (
    <main>
      <button onClick={signOut}>Sign out</button>
-      <h1>My todos</h1>
+      <h1>{user?.signInDetails?.loginId}'s todos</h1>
     
     {/* ... */}
    
    </main>
  );
}

```

ここまでの変更をコミットしてプッシュしておきましょう。
プッシュをするとAmplify側で自動的に新しいビルドが動いてAmplify上でも動きを確認できるようになります。

ログイン画面が見えるようになり、アカウントの作成後、サインインができるようになります。そして、ログインユーザーのメールアドレスが画面上で表示されるようになりました。またログイン後のサインアウトボタンを追加できているので、Amplifyにデプロイされた動きを確認してみてください

長かったですが、下ごしらえはここまで。

## Storage の追加

https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser#amplify-auth

ここからは、いよいよStorage Browser for Amazon S3 のドキュメントを見ながら進めていきます。まずは使用するS3を作成します。Amplify Gen 2 の機能を使って追加してみます。

`amplify/storage/resource.ts` に以下の内容のファイルを作成します。ここではS3バケットとprefix ごとの制御に関して書いています。

```typescript:amplify/storage/resource.ts
import { defineStorage } from '@aws-amplify/backend';

export const storage = defineStorage({
  name: 'myProjectFiles',
  access: (allow) => ({
    'public/*': [
      allow.guest.to(['read']),
      allow.authenticated.to(['read', 'write', 'delete']),
    ],
    'protected/{entity_id}/*': [
      allow.authenticated.to(['read']),
      allow.entity('identity').to(['read', 'write', 'delete'])
    ],
    'private/{entity_id}/*': [
      allow.entity('identity').to(['read', 'write', 'delete'])
    ]
  })
});
```

合わせて、`amplify/backend.ts` も以下のように書き換えます。「今回は Storage の機能のみを使用するため、data の定義は削除します。

```diff_typescript:amplify/backent.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
- import { data } from './data/resource';
+ import { storage } from './storage/resource';

defineBackend({
  auth,
-  data,
+  storage,  
});
```


## Storage Browser for Amazon S3 をサンプルアプリに組み込む

いよいよです。まずは以下のコマンドを叩いて、npmパッケージを追加しておきます。

```shell
$ npm install @aws-amplify/ui-react-storage
```

### Storage のコンポーネントを作成

`src/App.tsx` の部分を書き換えます。ToDoリストの部分を今回々っといらないので、削除しつつ、Storage Browser に関する部分だけを書いています。

また、クイックスタートではログインIDを指定して表示させていましたが、ユーザー名（UUID）に変更しておきましょう。

変化が大きすぎるので削除した部分は明記しません


```diff_tsx:src/App.tsx

import {
  createAmplifyAuthAdapter,
  createStorageBrowser,
} from '@aws-amplify/ui-react-storage/browser';
import "@aws-amplify/ui-react-storage/styles.css";
import { Amplify } from "aws-amplify";
import { useAuthenticator } from '@aws-amplify/ui-react';
import config from '../amplify_outputs.json';

Amplify.configure(config);

export const { StorageBrowser } = createStorageBrowser({
  config: createAmplifyAuthAdapter(),
});

function App() {

  const { user, signOut } = useAuthenticator();

  return (
    <main>
      <button onClick={signOut}>Sign out</button>
-      <h1>{user?.signInDetails?.loginId}'s todos</h1>
+      <h1>{user?.username}</h1>
      <StorageBrowser/>
    </main>
  );
}

export default App;

```
`src/main.tsx` も少し変更します。初期のCSSが当たってしまうと表示が崩れてしまうので、削除します。

```diff_tsx:src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { Authenticator } from '@aws-amplify/ui-react';
import App from "./App.tsx";
- import "./index.css";
import '@aws-amplify/ui-react/styles.css';
import { Amplify } from "aws-amplify";
import outputs from "../amplify_outputs.json";

Amplify.configure(outputs);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <Authenticator>
      <App />
    </Authenticator>
  </React.StrictMode>
);

```

ここまでの変更をコミットしてプッシュしてAmplifyでのビルドを待ってみましょう。

無事に表示ができました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/d3616255-f0d2-1ed2-052d-fe69671aa5aa.png)

### アカウントごとの動きの確認。


`amplify/storage/resource.ts`で定義した制御の動きを確認してみましょう。
private と public にフォルダーを作り、その中に画像をアップロードしてみましょう。

それができたら、一度サインアウトを行い、新しいアカウントを作って、同様に private と public にフォルダーを作り、その中に画像をアップロードしてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/c903ee43-6521-fe37-5cfc-0ea880f2712b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/32f67871-f8ef-19d7-a9a3-974e76ab14b3.png)


private では自分で作ったものだけを触ることができるようになっていて、 public 配下では全て触れるようになっています。

`amplify/storage/resource.ts`で定義した制御に関しては以下のドキュメントを参照してください。

https://docs.amplify.aws/react/build-a-backend/storage/authorization/

Storage Browser for Amazon S3 を通してはユーザーごとに違う動きをしていることが確認できますが、AmplifyコンソールのStorageタブからは管理者権限的に全てのファイルの操作をすることが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927755/dc870ef8-2986-6e80-db0a-7330dcadddf2.png)


## さいごに

個人的な経験だとS3の制御をIAMユーザーを持っていない人に渡すときはFTPクライアントでの環境を渡すことが多かったのですが、従来のFTPクライアントを用いた方法では、ファイル操作の細かな制御が難しかったです。（何か方法があったのかもしれませんが、、、）

Storage Browser for Amazon S3 を通した環境であれば、より柔軟でコントロールされた状態でのS3の環境を公開することができるようになるので、適用できるシーンが多いのではないでしょうか

### Storage-JAWS で登壇しました
2024年12月20日に実施された　Storage-JAWS にて、このテーマで登壇しましたので、資料を貼っておきます。
AWS Transfer Family Web Apps との比較なども加えているので、参考までに。

<script defer class="speakerdeck-embed" data-id="c49a106a7154473994bf5851c0458060" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

ぜひ、皆さんも使ってみてください