Rustのreqwestでcertificate verify failed", file: "../ssl/statem/statem_clnt.c"というエラーが出る

# はじめに

RustでHttps経由で外部のAPIを叩くエンドポイントを作成したところ、デプロイしたあとに500系のエラーが発生していたので対処をしました

その過程を忘れないようにまとめておきます

# 問題

以下のコードを実装しました

```rs:main.rs
#[tokio::main]
async fn main() {
    let client = reqwest::Client::new();
    let result = client.get("https://jsonplaceholder.typicode.com/users/1").send().await;

    println!("{:?}", result);
}
```

そしてデプロイをして実行すると以下のエラーが発生しました

```
Err(reqwest::Error { kind: Request, url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("jsonplaceholder.typicode.com")), port: None, path: "/users/1", query: None, fragment: None }, source: hyper::Error(Connect, Ssl(Error { code: ErrorCode(1), cause: Some(Ssl(ErrorStack([Error { code: 337047686, library: "SSL routines", function: "tls_process_server_certificate", reason: "certificate verify failed", file: "../ssl/statem/statem_clnt.c", line: 1916 }]))) }, X509VerifyResult { code: 20, error: "unable to get local issuer certificate" })) })
```

# 検証

まずはローカルで実行します

```
$ cargo run
```

すると問題なくレスポンスが返ってきました

```
Ok(Response { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("jsonplaceholder.typicode.com")), port: None, path: "/users/1", query: None, fragment: None }, status: 200, headers: {"date": "Thu, 29 Jun 2023 01:50:33 GMT", "content-type": "application/json; charset=utf-8", "content-length": "509", "connection": "keep-alive", "x-powered-by": "Express", "x-ratelimit-limit": "1000", "x-ratelimit-remaining": "999", "x-ratelimit-reset": "1686752728", "vary": "Origin, Accept-Encoding", "access-control-allow-credentials": "true", "cache-control": "max-age=43200", "pragma": "no-cache", "expires": "-1", "x-content-type-options": "nosniff", "etag": "W/\"1fd-+2Y3G3w049iSZtw5t1mzSnunngE\"", "via": "1.1 vegur", "cf-cache-status": "HIT", "age": "9476", "accept-ranges": "bytes", "report-to": "{\"endpoints\":[{\"url\":\"https:\/\/a.nel.cloudflare.com\/report\/v3?s=AZ%2B10legeLukYLv3vRkA5DlgJgxxM7z%2B3CcwUoKcPnAUGdLb6vjFMoDiXfE5sDcSSlD81xx1290OKGWXfPYi%2Bs6RTS5ikwTcgGvOdIdvmNy4lcNUm3QKB8qVa5lPP9sGmhOwE392Z5zpaFjI6azm\"}],\"group\":\"cf-nel\",\"max_age\":604800}", "nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}", "server": "cloudflare", "cf-ray": "7dea84f18b2d25fe-NRT", "alt-svc": "h3=\":443\"; ma=86400"} })
```

次にデプロイしたK8sのポッドの中からcurlで確認します

```
$ kubectl --context=test -n api rust-reqwest -- bash
$ curl https://jsonplaceholder.typicode.com/users/1
```

こちらも問題なく返ってきました
このことからコンテナ自身に問題がありそうだと予測できました

まずはコンテナをローカルでビルドします

```Dockerfile:Dockerfile
FROM rust:1.69-slim-buster as build-stage
WORKDIR /build
RUN apt-get update && \
    apt-get install -y curl libssl-dev pkg-config build-essential
COPY . .
RUN cargo test && \
    cargo build --release

FROM debian:buster-slim
RUN apt-get update && apt-get install -y libssl-dev
COPY --from=build-stage /build/target/release/rust-reqwest /usr/bin

EXPOSE 10500
CMD ["/usr/bin/rust-reqwest"]
```

```
$ docker build . -t rust-reqwest
$ docker run rust-reqwest
```

すると同じエラーが発生しました

# 解決方法

Dockerfileに`ca-certificates`を追加することで解決しました

```Dockerfile
FROM rust:1.69-slim-buster as build-stage
WORKDIR /build
RUN apt-get update && \
    apt-get install -y curl libssl-dev pkg-config build-essential
COPY . .
RUN cargo test && \
    cargo build --release

FROM debian:buster-slim
RUN apt-get update && apt-get install -y libssl-dev ca-certificates
COPY --from=build-stage /build/target/release/rust-reqwest /usr/bin

EXPOSE 10500
CMD ["/usr/bin/rust-reqwest"]
```
```
$ docker build . -t rust-reqwest
$ docker run rust-reqwest

Ok(Response { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("jsonplaceholder.typicode.com")), port: None, path: "/users/1", query: None, fragment: None }, status: 200, headers: {"date": "Thu, 29 Jun 2023 01:50:33 GMT", "content-type": "application/json; charset=utf-8", "content-length": "509", "connection": "keep-alive", "x-powered-by": "Express", "x-ratelimit-limit": "1000", "x-ratelimit-remaining": "999", "x-ratelimit-reset": "1686752728", "vary": "Origin, Accept-Encoding", "access-control-allow-credentials": "true", "cache-control": "max-age=43200", "pragma": "no-cache", "expires": "-1", "x-content-type-options": "nosniff", "etag": "W/\"1fd-+2Y3G3w049iSZtw5t1mzSnunngE\"", "via": "1.1 vegur", "cf-cache-status": "HIT", "age": "9476", "accept-ranges": "bytes", "report-to": "{\"endpoints\":[{\"url\":\"https:\/\/a.nel.cloudflare.com\/report\/v3?s=AZ%2B10legeLukYLv3vRkA5DlgJgxxM7z%2B3CcwUoKcPnAUGdLb6vjFMoDiXfE5sDcSSlD81xx1290OKGWXfPYi%2Bs6RTS5ikwTcgGvOdIdvmNy4lcNUm3QKB8qVa5lPP9sGmhOwE392Z5zpaFjI6azm\"}],\"group\":\"cf-nel\",\"max_age\":604800}", "nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}", "server": "cloudflare", "cf-ray": "7dea84f18b2d25fe-NRT", "alt-svc": "h3=\":443\"; ma=86400"} })
```

# おわりに

先輩の解決までの思考などを学びながら自分でもすばやく解決できるようになりたいなと思いました

# 参考

https://jsonplaceholder.typicode.com/users/1