OpenSearchで対象のIPアドレスを検索結果から除外するクエリ

# やりたいこと
OpenSearchのAlertingのクエリで、指定のIPアドレスを除外して検索したいことがありました。
いくつかの方法で除外できたので、その方法を記載します。
また、今回はAlertingで試しましたが、[クエリDSL](https://opensearch.org/docs/latest/opensearch/query-dsl/index/)が実行できればどこでも利用可能です。

# 前提
Amazon OpenSearch Serviceバージョン1.2での内容になります。
OpenSearch ServerlessやElasticsearch、OSSのOpenSearchでは挙動が異なる場合があるのでご注意ください。

# IPアドレスの除外指定方法
まず、指定するフィールドのtypeはIP address fieldであることが前提となります。
今回の記事の例では、`source.ip`というフィールドがIP address fieldとなっています。
[公式ドキュメント](https://opensearch.org/docs/latest/opensearch/supported-field-types/ip/)に、IP address fieldについての説明があります。ここに記載がある通り、**IPv4,IPv6どちらにも対応**しています。

それでは、3つの方法を紹介します。
どの方法も、`"must_not"`で除外対象を指定するところまでは一緒ですが、それより下の除外するIPの指定の方法が異なります。`"must_not"`を含めた、AND/OR/NOTのbool句の指定は、[ここ](https://opensearch.org/docs/latest/opensearch/query-dsl/bool/)に詳しく乗っています。

## ①termでCIDR指定
CIDR表記で除外するIPアドレスの範囲を指定したい場合です。
`"term"`では通常は1つの値のみしか指定できませんが、IPアドレスではCIDR表記で指定することが可能です。

以下の例では、`13.99.0.0`の最初の16ビット（13.99までの部分）が同じであるIPを除外します。
つまり、`13.99.0.1`や`13.99.12.101`などを一気に除外することができます。

```json
{
    "bool": {
        "must_not": [
            {
                "term": {
                    "source.ip": "13.99.0.0/16"
                }
            }
        ],
        "adjust_pure_negative": true,
        "boost": 1
    }
}
```

## ②terms指定
IPアドレスを複数指定したい場合です。
`"terms"`を使って、複数の値を指定して除外できます。

以下の例では、
`2001:db8:1::ab9:C0A8:102`（IPv6アドレス）
`10.20.30.4`
`192.0.0.9`
の3つのIPアドレスを除外します。

```json
{
    "bool": {
        "must_not": [
            {
                "terms": {
                    "source.ip": [
                        "2001:db8:1::ab9:C0A8:102",
                        "10.20.30.4",
                        "192.0.0.9"
                    ]
                }
            }
        ],
        "adjust_pure_negative": true,
        "boost": 1
    }
}
```

## ③range指定
①と似ていますが、明示的にIPアドレスの範囲を指定したい場合です。
`"range"`では、最初と最後のIPアドレスを指定して、範囲を指定できます。

以下の例では、IPv6とIPv4の2つの範囲を指定しています。
`2001:db8:1::ab9:C0A8:103`や`192.0.0.55`などを除外することができます。

```json
{
    "bool": {
        "must_not": [
            {
                "range": {
                    "source.ip": {
                        "from": "2001:db8:1::ab9:C0A8:102",
                        "to": "2001:dc8:1::ab9:C0A8:106",
                        "include_lower": true,
                        "include_upper": true,
                        "boost": 1
                    }
                }
            },
            {
                "range": {
                    "source.ip": {
                        "from": "192.0.0.0",
                        "to": "192.0.0.99",
                        "include_lower": true,
                        "include_upper": true,
                        "boost": 1
                    }
                }
            }
        ],
        "adjust_pure_negative": true,
        "boost": 1
    }
}
```

また、ネットを検索すると`"range"`句を以下のようにgte,lteを用いて書く方法を目にします。
この方法でAlertのクエリを更新し、AlertのUpdateをすると、上記のfrom,toを用いる方法に自動的に書き換わりました。
少し古い記法なのかもしれません。
```json
"range": {
    "source.ip": {
        "gte": "192.0.0.0",
        "lte": "192.0.0.99"
    }
}
```

# 注意点
クエリで、[wildcards](https://opensearch.org/docs/latest/opensearch/query-dsl/term/#wildcards)を用いて検索する方法があります。
しかし、**wildcardsはstring typeのフィールドにしか使えない**ため、ipアドレスのフィールドには利用できないのでご注意ください。

まぁ、今回の3つの方法のどれかで大体の要件は満たせるのではないかなと思います。

# おわりに
なかなかIPアドレスの除外というところに絞ったノウハウはなかったため、記事にしてみました。
フィールドのtypeでipが選べるということもあって、なかなか柔軟に検索ができますね。
ちなみに、[ip_adress_range](https://opensearch.org/docs/latest/opensearch/supported-field-types/range/#ip-address-ranges)というtypeもあります。これは、IPアドレス単体ではなくIPアドレスレンジ自体を表すtypeとのことでした。OpenSearch、奥深い。

# 参考
https://georgebridgeman.com/posts/ip-field-types/