AWS-SDKを組み込んだC++/Rustコードを、WebAssemblyに変換できなかった話

# サマリ
本記事は、AWS-SDKを組み込んだC++やRustを、WebAssemblyへ変換を試みて失敗した経緯を記載しています。
同様の試みを検討している方のご参考になれば幸いです。

# 経緯
ブラウザアプリにて、フロントエンド側のみでAWS-SDKを利用してS3へオブジェクトのPUTをしたいと思いました。
課題となるのはAWSのアクセスキーとシークレットアクセスキーです。
HTMLやJavaScriptではコードが丸見えになってしまうので、これらのキーを隠蔽することができません。

そこでWebAssemblyならバイナリ化されているので隠蔽できるのでは？ と思い、当該処理をWebAssemblyで実現しようと思い立ちました。

なお後で知りましたが、WebAssemblyはブラウザから普通にコードが読めてしまうのでこの期待はそもそも成立しないものでしたが……

<details><summary>WebAssemblyのコードがブラウザでよめる例</summary><div>
下図はWebAssemblyのコードをブラウザの開発ツールで参照した例です。
人間が解読可能なコードに変換されており、`This message don't have to publish anyone!!` という固定値もバッチリ読めます。

![スクリーンショット 2021-04-30 17.05.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119348/6a345d9f-baa2-9e4d-57bd-dd773d710b51.png)

元となったコードはこちら↓

```c:main.c
#include <stdio.h>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {
  printf("Hello World\n");
}

#ifdef __cplusplus
extern "C" {
#endif

void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
  static const char* KEY = "This message don't have to publish anyone!!"; 
  printf("MyFunction Called%s\n", KEY);
}

#ifdef __cplusplus
}
#endif
```
</div></details>


# 環境

今回の検証環境は下記のとおり。

### マシン

```sh
$sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.5
BuildVersion:   19F101
```

### Cコンパイラ

```sh
$make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for i386-apple-darwin11.3.0
```

```sh
$cmake --version
cmake version 3.19.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).
```

```sh
$gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.32.29)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
```

### Emscripten SDK
WebAssemblyに変換するツールとしてEmscripten SDKを利用します。
下記サイトを参考にインストールしてください。（検証では2.0.18を利用）
[https://emscripten.org/docs/getting_started/downloads.html](https://emscripten.org/docs/getting_started/downloads.html)

### AWS環境
下記をあらかじめ用意。

* アップロード先のS3バケット
* S3にPUTする権限をもつIAMロール
    * このロールのAWSアクセスキー 、AWSシークレットアクセスキーを利用します

## S3アップロード処理(C++)の作成
### 「AWS SDK for C++」のインストール
vcpkgというC/C++のパッケージマネージャを利用して、AWS SDK for C++をインストールします。

- vcpkgをクローン

```sh
$ mkdir aws-sdk-s3-test
$ cd aws-sdk-s3-test
$ git clone https://github.com/microsoft/vcpkg.git
```

- ブートストラップコマンドを実行

```sh
$ ./vcpkg/bootstrap-vcpkg.sh
```

- s3パッケージのインストール

```sh
# s3パッケージだけインストールする
$ ./vcpkg/vcpkg install "aws-sdk-cpp[s3]" --recurse
```

### C++のコード実装

`aws-sdk-s3-test`ディレクトリに下記2つのファイルを作成します。

```cpp:main.cpp
#include <aws/s3/S3Client.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/memory/stl/AWSStringStream.h> 

using namespace Aws::S3;
using namespace Aws::S3::Model;

static const char* KEY = "<オブジェクトのキー>";
static const char* BUCKET = "<S3のバケット名>";
const Aws::String AWS_ACCESS_KEY_ID = "<AWSのアクセスキー>";
const Aws::String AWS_SECRET_ACCESS_KEY = "<AWSのシークレットアクセスキー>";

# S3にHello Worldと記載したテキストをアップロードする
int main(int argc, char** argv)
{
    Aws::SDKOptions options;
    Aws::InitAPI(options);
    {
        Aws::Client::ClientConfiguration config;
        config.scheme = Aws::Http::Scheme::HTTPS;
        config.connectTimeoutMs = 30000;
        config.requestTimeoutMs = 30000;
        config.region = Aws::Region::AP_NORTHEAST_1;
s3Client(Aws::Auth::AWSCredentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), config);

        PutObjectRequest putObjectRequest;
		putObjectRequest.WithKey(KEY)
			   .WithBucket(BUCKET);

        auto requestStream = Aws::MakeShared<Aws::StringStream>("s3-sample");
		*requestStream << "Hello World!";
		putObjectRequest.SetBody(requestStream);

		auto putObjectOutcome = s3Client.PutObject(putObjectRequest);

		if(putObjectOutcome.IsSuccess())
		{
			std::cout << "Put object succeeded" << std::endl;
		}
		else
		{
			std::cout << "Error while putting Object " << putObjectOutcome.GetError().GetExceptionName() << 
				   " " << putObjectOutcome.GetError().GetMessage() << std::endl;
		}
    }
    Aws::ShutdownAPI(options);
    return 0;  
}
```

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.2)
# CMAKE_CURRENT_SOURCE_DIR 現在処理中の CMakeLists.txt の配置ディレクトリ
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
project(sample-s3)
set(CMAKE_CXX_STANDARD 17)

add_executable(main main.cpp)
find_package(AWSSDK CONFIG COMPONENTS s3 REQUIRED)
target_link_libraries(main PRIVATE ${AWSSDK_LIBRARIES})
```
### c++のビルド・実行
`aws-sdk-s3-test`ディレクトリ上で下記コマンド実行します。

```sh
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release
$ make
```

生成された`main`を実行します。S3にオブジェクトがアップロードされていることを確認できます。

```sh
$ .main
Put object succeeded
```
### WebAssemblyへの変換（失敗）
WebAssemblyを生成するためにEmscripten SDKを利用する。

#### Try1： emcmakeをそのまま実行
Emscripten SDKのemcmakeはcmakeコマンドをラップして実行できるとのことで、まずはそのまま実行。

```sh
$ emcmake cmake .. -DCMAKE_BUILD_TYPE=Release
```

すると下記のエラーがでます。

```sh
CMake Error at CMakeLists.txt:11 (find_package):
  Could not find a package configuration file provided by "AWSSDK" with any
  of the following names:

    AWSSDKConfig.cmake
    awssdk-config.cmake

  Add the installation prefix of "AWSSDK" to CMAKE_PREFIX_PATH or set
  "AWSSDK_DIR" to a directory containing one of the above files.  If "AWSSDK"
  provides a separate development package or SDK, be sure it has been
  installed.


-- Configuring incomplete, errors occurred!
```

CMakeLists.txtにて、`CMAKE_TOOLCHAIN_FILE`の設定によりAWSSDKのパッケージも見つけられるはずですが、emcmakeで実行するとなぜかうまく動作しないようです。

#### Try2： emcmakeに環境変数を渡す

上記方法ではうまくいかなかったので、コマンドから環境変数で直接指定します。

CMakeLists.txtにて、`CMAKE_TOOLCHAIN_FILE` の指定はコメントアウトします。

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.2)
# CMAKE_CURRENT_SOURCE_DIR 現在処理中の CMakeLists.txt の配置ディレクトリ
# set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
project(sample-s3)
set(CMAKE_CXX_STANDARD 17)

add_executable(main main.cpp)
find_package(AWSSDK CONFIG COMPONENTS s3 REQUIRED)
target_link_libraries(main PRIVATE ${AWSSDK_LIBRARIES})
```

代わりにemacmake実行時に環境変数として渡してあげます。

```sh
# 念の為絶対パスで指定
$ emcmake cmake .. -DCMAKE_TOOLCHAIN_FILE="/Users/<ユーザ名>/Public/develop/cpp/aws-sdk-s3-test/vcpkg/scripts/buildsystems/vcpkg.cmake"
```

コンパイル処理が開始しましたが、下記のとおり失敗しました。

```sh
CMake Error at /usr/local/Cellar/cmake/3.19.2/share/cmake/Modules/CMakeTestCCompiler.cmake:66 (message):
  The C compiler

    "/Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/emcc"

  is not able to compile a simple test program.

  It fails with the following output:

    Change Dir: /Users/<ユーザ名>/Public/develop/cpp/aws-sdk-s3-test/build/CMakeFiles/CMakeTmp
    
    Run Build Command(s):/usr/bin/make cmTC_a6cd1/fast && /Applications/Xcode.app/Contents/Developer/usr/bin/make  -f CMakeFiles/cmTC_a6cd1.dir/build.make CMakeFiles/cmTC_a6cd1.dir/build
    Building C object CMakeFiles/cmTC_a6cd1.dir/testCCompiler.c.o
    /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/emcc   -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -mmacosx-version-min=10.15 -o CMakeFiles/cmTC_a6cd1.dir/testCCompiler.c.o -c /Users/<ユーザ名>/Public/develop/cpp/aws-sdk-vcpkg-test/build/CMakeFiles/CMakeTmp/testCCompiler.c
    clang-13: warning: argument unused during compilation: '-mmacosx-version-min=10.15' [-Wunused-command-line-argument]
    Linking C executable cmTC_a6cd1
    /usr/local/Cellar/cmake/3.19.2/bin/cmake -E cmake_link_script CMakeFiles/cmTC_a6cd1.dir/link.txt --verbose=1
    /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/emcc  -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -mmacosx-version-min=10.15 -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/cmTC_a6cd1.dir/testCCompiler.c.o -o cmTC_a6cd1 
    wasm-ld: error: unknown argument: -search_paths_first
    wasm-ld: error: unknown argument: -headerpad_max_install_names
    emcc: error: '/Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/bin/wasm-ld -o cmTC_a6cd1.wasm -search_paths_first -headerpad_max_install_names CMakeFiles/cmTC_a6cd1.dir/testCCompiler.c.o -L/Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libgl.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libal.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libhtml5.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libc.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libcompiler_rt.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libc++-noexcept.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libc++abi-noexcept.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libdlmalloc.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libc_rt_wasm.a /Users/<ユーザ名>/Public/develop/cpp/emsdk/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libsockets.a -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --allow-undefined --strip-debug --export main --export emscripten_stack_get_end --export emscripten_stack_get_free --export emscripten_stack_init --export stackSave --export stackRestore --export stackAlloc --export __wasm_call_ctors --export fflush --export __errno_location --export-table --export __start_em_asm --export __stop_em_asm -z stack-size=5242880 --initial-memory=16777216 --no-entry --max-memory=16777216 --global-base=1024' failed (1)
    make[1]: *** [cmTC_a6cd1] Error 1
    make: *** [cmTC_a6cd1/fast] Error 2
    
    

  

  CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
  CMakeLists.txt:6 (project)


-- Configuring incomplete, errors occurred!
```

本記事の試行はここまです。

# おまけ：Rustのケース
c++でなくRustでも試行して失敗しました。

```toml:Cartgo.toml
[package]
name = "vc-person-image-uploader"
version = "0.1.0"
edition = "2018"

[dependencies]
rusoto_core = "0.46.0"
rusoto_s3 = "0.46.0"
serde_json = "1.0.40"

[lib]
crate-type = ["cdylib"]
```

```rs:src/lib.rs
use rusoto_core::{Region};
use rusoto_s3::{S3, S3Client, PutObjectRequest};
use serde_json::Value;

#[no_mangle]
pub fn send() {
    // 環境変数から取得する
    std::env::set_var("AWS_ACCESS_KEY_ID", "<AWSアクセスキー >");
    std::env::set_var("AWS_SECRET_ACCESS_KEY", "<AWSシークレットアクセスキー>");

    // クライアントの作成
    let client = S3Client::new(Region::ApNortheast1);

    // JSONの作成
    let data = r#"{
        "name": "サンプル",
        "value": 123456
    }"#;
    let value: Value = serde_json::from_str(&data).unwrap();

    // リクエスト作成
    let mut request = PutObjectRequest::default();
    request.bucket = String::from("<バケット名>");
    request.key = String::from("<キー名>");
    request.body = Some(format!("{}", value).into_bytes().into());

    // アップロード
    let _result = client.put_object(request).sync().unwrap();
}

```

WebAssemblyへのコンパイルを実行したところエラーとなりました。

```sh
$ wasm-pack build --target web
```

<details><summary>エラー内容の詳細</summary><div>

```sh
[INFO]: 🎯  Checking for the Wasm target...
[INFO]: 🌀  Compiling to Wasm...
   Compiling socket2 v0.4.0
   Compiling subtle v2.4.0
   Compiling dirs-sys-next v0.1.2
   Compiling discard v1.0.4
error[E0432]: unresolved import `crate::sys`
 --> /Users/<ユーザ名>/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.0/src/sockaddr.rs:5:12
  |
5 | use crate::sys::{
  |            ^^^
  |            |
  |            unresolved import
  |            help: a similar path exists: `crate::socket::io::sys`

error[E0432]: unresolved imports `crate::sys`, `crate::sys`
  --> /Users/<ユーザ名>/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.0/src/socket.rs:21:12
   |
21 | use crate::sys::{self, c_int, getsockopt, setsockopt, Bool};
   |            ^^^   ^^^^ no `sys` in the root
   |            |
   |            unresolved import
   |            help: a similar path exists: `crate::socket::io::sys`

error[E0432]: unresolved import `sys`
   --> /Users/<ユーザ名>/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.0/src/lib.rs:129:5
    |
129 | use sys::c_int;
    |     ^^^ use of undeclared crate or module `sys`


<中略>

note: associated function defined here

   Compiling base64 v0.13.0
error: aborting due to 25 previous errors

Some errors have detailed explanations: E0061, E0308, E0432, E0433.
For more information about an error, try `rustc --explain E0061`.
error: could not compile `socket2`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed
Error: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit code: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"

```
</div></details>


dependenciesに `rusoto_core`（Rust用のAWS-SDK） を含めるだけでエラーとなるので、そもそも対応していないと思われます。

# おわりに
AWS-SDKを利用したWebAssemblyの構築は失敗に終わりました。
Emscriptenによる変換ではそもそも対応していないのかもしれません。
clangのコンパイル経由ならどうかとも思いましたが、経緯に記載したとおり、キーを隠蔽したいという当初の目標はWebAssemblyではそもそも叶わないので検証はやめました。

S3へのPUT方法は、ユーザ認証をしたサーバ側から、アップロード用の署名付きURLを渡してもらう、という正統派の方法でセキュリティを担保しようと今は考えています。

# 参考サイト
* [C/C++からWebAssemblyにコンパイルする](https://developer.mozilla.org/ja/docs/WebAssembly/C_to_wasm)
* [vcpkgでAWS SDK for C++をインストールする](https://dev.classmethod.jp/articles/install-aws-sdk-cpp-vcpkg/)
* [Rust から WebAssembly にコンパイルする](https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm)