OWASP ZAPで診断するためにAWS CDKで実装が必須となる箇所

## はじめに

AWSのS3, CloudFront, API-Gateway, Lambdaなどを使って実装したサービスに対して、CICDパイプラインの一部として脆弱性診断を流せるか？という確認作業の中で、診断ツールの1つであるOWASP ZAPを試しています。

2021年8月時点、AWS CDKを使って定義したAPI Gatewayに対して、[OWASP ZAP Docker](https://www.zaproxy.org/docs/docker/)を使って脆弱性診断をおこなう際、API定義の内容によって診断できない場合がありました。
診断できない2つのケースとその対処法について説明します。

1. 診断できないケース1：Status Codeの定義漏れ
1. 診断できないケース2：Request Parametersの定義漏れ

※ 以下、動作確認で使用したコンテナイメージは `owasp/zap2docker-stable` になります。

## 診断できないケース1：Status Codeの定義漏れ

### 診断NGとなるCDKのソースコード

```typescript
// <中略>
//  GETメソッドが返すレスポンスの定義がない
const userId = users.addResource("{userId}")
userId.addMethod("GET")
// <以下省略>
```

### 定義がないとどうなるか

- 2021/07月頃はNullPointerExceptionで落ちて終了していました
- 2021/08/19以降は、zap起動後に、以下のwarningが出て診断が実行されずに終了しました。
- 今回の問題解決のヒントとなる`Import warnings`のログはzapのシェルスクリプトを起動してすぐ出力されているのですが、その後Add-onのログが大量に出て流れてしまうので、見落としに注意が必要です。

```shell
2021-08-19 07:04:13,560 Import warnings: ['attribute openapi is missing', 
"attribute paths.'/users'(post).responses is missing", 
"attribute paths.'/users/{userId}'(get).responses is missing", 
"attribute paths.'/users/{userId}'(delete).responses is missing"]
2021-08-19 07:04:13,560 Failed to import any URLs
Traceback (most recent call last):
  File "/zap/zap-api-scan.py", line 456, in main
    raise NoUrlsException()
NoUrlsException
# 中略：Add-onをロードするログが続く
9160 [ZAP-daemon] INFO  org.parosproxy.paros.CommandLine - Add-on downloaded to: /home/zap/.ZAP_D/plugin/pscanrulesAlpha-alpha-33.zap
9161 [ZAP-daemon] INFO  org.zaproxy.zap.DaemonBootstrap - ZAP is now listening on 0.0.0.0:37715
2021-08-19 07:04:13,562 Trigger hook: pre_exit, args: 3
```

### 診断OKなCDKのソースコード

```typescript
// --- 診断OKなCDKのソースコード ---
const userId = users.addResource("{userId}")
userId.addMethod("GET", 
  integration,
  { // 最低限これがあれば診断エラーがでない状態。書き方の詳細はAPIドキュメント参照
    methodResponses: [{statusCode: '200' }],
  }
);
// <以下省略>
```
- 参考: [MethodOptions#methodResponses](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.MethodOptions.html#methodresponses)

### 補足

API仕様的には以下のようにaddResourceの第二引数の中にdefaultMethodOptionsの値として定義することも可能のようですが、実際やってみると、API GatewayからエクスポートしたOpenAPI定義ファイルでは各メソッドのresponseとしては定義されず、上記と同じエラーになりました。

```typescript
const userId = users.addResource("{userId}", {
	// NG:このようにaddResourceの引数で与えても各メソッドのレスポンス定義には
      // 反映されなかった
    defaultMethodOptions: {
		methodResponses: [{statusCode: '200' }]
    }
})
```

## 診断できないケース2：Request Parametersの定義漏れ

OWASP ZAP(Docker)で診断する際は、パスやクエリにあるパラメータをCDKのコード上で定義してやる必要がありました。

例えば以下の例では、userIdというパスパラメータを使っています。

### 診断NGなCDKのソースコード

```typescript
// path parameter {userId}の定義をしていない
const userId = users.addResource("{userId}") 
userId.addMethod("GET", integration,
    {
        methodResponses: [{statusCode: '200' }],
    }
);
```

### 定義がないとどうなるか

- api scan実行時に以下のような「parameterの定義がないよ」というwarningが出て、（add onをロードしたログが出た後に）診断されず処理が終わります。
- 2021/08/04ぐらいから発生し始めた現象です

```shell
2021-08-19 05:11:32,080 Import warnings: ['attribute openapi is missing', 
"attribute paths.'/users/{userId}'. Declared path parameter userId needs
 to be defined as a path parameter in path or operation level"]
2021-08-19 05:11:32,080 Failed to import any URLs
Traceback (most recent call last):
  File "/zap/zap-api-scan.py", line 456, in main
    raise NoUrlsException()
NoUrlsException
<中略: Add-onの読み込みログ>
2021-08-19 05:11:43,562 Trigger hook: pre_exit, args: 3
```


### 診断OKなCDKのソースコードの書き方：その1

```typescript
// addResourceの第二引数にresourceOptionsを定義してその中に
// defaultMethodOptions>requestParametersを定義
const userId = users.addResource("{userId}", {
    defaultMethodOptions: {
        requestParameters:{'method.request.path.userId':true}
    }
})
userId.addMethod("GET", integration,
    {
        methodResponses: [{statusCode: '200' }]
    }
);
```
addResourceの第二引数がresourceOptionsになります。そのプロパティとして、defaultMethodOptions>requestParametersを追加してuserIdを宣言します。詳細な仕様は以下ドキュメントを参照してください。

- 参考: [ResourceOptions#defaultmethodoptions](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.ResourceOptions.html#defaultmethodoptions)


### 診断OKなCDKのソースコードの書き方：その2

```typescript
// addResourceはそのまま。代わりに”GET"などメソッド毎に
// requestParametersを定義する
// ただしこの場合、(OPTIONS含め)定義する全てのメソッドに
// requestParametersを定義する必要がある
const userId = users.addResource("{userId}")
userId.addMethod("GET", integration,
    {
        methodResponses: [{statusCode: '200' }],
	    requestParameters:{'method.request.path.userId':true}
    }
);
// 以下省略
```
こちらの書き方では、`addResource`はそのまま。代わりに”GET"などメソッド毎に `requestParameters`を定義します。

- 参考: [MethodOptions#requestparameters](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.MethodOptions.html#requestparameters)

ただしこの場合、（OPTIONS含め）定義するすべてのメソッドに`requestParameters`を定義する必要があります。
上記サンプルコード中のクエリパラメータを示すキー `method.request.path.userId`の書き方は以下のサイトに書き方の例があります。

- 参考: [request-response-data-mappings](https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html)
- たとえばメソッドのクエリ文字列であれば、`method.request.querystring.PARAM_NAME`のようになります
- booleanの値部分は必須パラメータかどうかを示しています


## おわりに

2021年7～8月にOWASP ZAP Dockerを使って遭遇した問題とその対処（結局は定義をちゃんと書きましょうということ）について共有させていただきました。