# Elasticsearch Token Service

## 前言

Elasticsearch 的 X-Pack Security 機制中，Token Based Authentication services 包含了兩種方式， `token-service` 與 `api-key-service` ，而 `api-key-service` 是 Basic License 就能使用的服務，不過 `token-service` 是必需要 Gold License 或是 Elastic Cloud service Standard 以上的授權才能使用的功能，這篇文章主要會介紹 `token-service` 的用法。

### 進入此章節的先備知識

* OAuth2 的運作方式。(可參考這篇：[簡單易懂的 OAuth 2.0](https://speakerdeck.com/chitsaou/jian-dan-yi-dong-de-oauth-2-dot-0))

### 此章節的重點學習

* 如何使用 Elasticsearch Token Service，也就是 Get token API。

***

## Elasticsearch Token Service

進到這篇文章時，已經假設讀者了解 OAuth2 的運作方式、也知道 OAuth2 的好處，而 Elastic Stack 在 X-Pack Security 的套件中，是以 `token-service` 的機制來實作 OAuth2 的 token 核發與更新機制。

### 基本介紹

在 Elastic Stack 中 `token-service` 的運作，主要就是以 [get token API](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api-get-token.html) 來產生 `access token` 和 `refresh token` ，這個 `access token` 是個短時效性的 token，預設是 20分鐘 過期，最長可以設到 1小時 過期，而核發 `access token` 時會同時提供一個 `refresh token` ，這個 `refresh token` 是當 `access token` 過期時，換發新的 `access token` 用的，預設是 24小時 有效。

### 啟用 Token Service

Elasticsearch 的架設上，如果有開啟 TLS (HTTPS) 的話，預設就會啟用 `token-service` ，或是可以手動在 `elasticsearch.yml` 的 config 中開啟：

```
xpack.security.authc.token.enabled: true
```

> 這邊要注意， 因為安全性的考量， Elasticsearch 在 production 環境提供的 bootstrap 啟動檢查時，會特別檢查如果有開啟 `token-service` 的話，必須要啟用 TLS ，否則 token 在不安全的傳輸過程中，是非常危險的。

如先前提到的，這個 `token-service` 必須是

* **Gold License** Subscription
* Elastic Cloud service **Standard** version

這兩種或是以上的授權等級，才能使用這項服務，如果是自架的 **Basic License** ，在使用 get token API 時，會得到以下的錯誤訊息：

![self-managed elasticsearch - basic license](https://i.imgur.com/byuZwuO.png)

### Get Token API

get token API 提供四種的 `grant_type`，分別的使用情境如下：

* `client_credentials`: 這是實作了 **OAuth2** 中的 **Client Credentials Grant** 的機制，主要是用在 machine to machine 的使用情境中，並不是提供給一般使用者操作的情境使用的，而簽發這個 token 時，會依照本來的 credential 的權限，產生一組 token 並付予完全一樣的權限，而這個機制只會提供 `access token` 不會提供 `refresh token`。
* `_kerberos`: 這是實作 **SPNEGO Kerberos** 機制，要使用的話，需要在 Elasticsearch 中設定好 [Kerberos realm 的配置](https://www.elastic.co/guide/en/elasticsearch/reference/current/kerberos-realm.html)。
* `password`: 這是實作 **Oauth2** 中的 **Resource Owner Password Credentials Grant** 的機制，這個機制是用在某一個已授權的用戶代表另一個授權的用戶來產生 `access token`，白話一點的說法就是，你要 call 這個 get token API 必須是已授權的某個使用者，但你帶入的 `username` 和 `password` 會是另一個人的帳號，並且是為了這個帳號來產生適合他權限的 `access token`，這個機制產生的 `access token` 會同時包含 `refresh token`，適合長時間的使用。
* `refresh_token`: 這是使用 `refresh_token` 來取的新的 `access token` 及 `refresh_token` 的時候所使用的。

以下有幾個例子：

#### Grant Type: `client_credentials`

這時因為在 call 這個 get token API 時，本身就會是以登入的身份 (或是帶某個 token 在 header 中)，所以只要帶這個 `grant_type` ，就會直接以當下使用者的身份權限，來產生出 `access token`。

```
POST /_security/oauth2/token
{
  "grant_type" : "client_credentials"
}
```

以下是回傳的結果，包含了 `access_token` 以及過期的時間，這種 `grant_type` 是不會有 `refresh_token` 的。

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200
}
```

取得這個 `access token` 後，要使用的時候，在 HTTP Authroization 中帶入這個 `Bearer` 的 `token` 即可：

```
curl -H "Authorization: Bearer dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==" http://localhost:9200/_cluster/health
```

#### Grant Type: `password`

下面是使用 `password` grant type 的例子，需帶入 `username` 與 `pasword`：

```
POST /_security/oauth2/token
{
  "grant_type" : "password",
  "username" : "test_admin",
  "password" : "x-pack-test-password"
}
```

回傳的結果如下：

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200,
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

可以看到這種 grant type 包含了 `refresh_token`，這個 `refresh_token` 的有效時間是 24小時，在這時限內，若 `access token` 過期，都能使用 `refresh_token` 來取得新的 `access_token`。

#### Grant Type: `refresh_token`

當 `access token` 過期時，若還在 `refresh_token` 有效的 24小時 之內，可以直接使用這種方式來取得新的 `access token` 和 `refresh token`。

```
POST /_security/oauth2/token
{
  "grant_type": "refresh_token",
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

以下是回傳結果：

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200,
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

### Token Service Settings

上面的例子的 `expires_in` 都是 `1200` ，如果我們要改變 `access token` 的有效時間，可以到 `elasticsearch.yml` 來改變這個時間的設定：

```
xpack.security.authc.token.timeout: 20m
```

預設是 `20m` ，最大可以設定的值是 1小時。

### Invalidate Token API

基本上 `access_token` 和 `refresh_token` 都有各自的過期時限，過期之後就無法再繼續使用。

如果要立即讓某個 token 失效、或某個使用者所有的 token 都失效時，就可以使用 `invalidate Token API`。

#### 讓某個 `access token` 立刻失效

```
DELETE /_security/oauth2/token
{
  "token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ=="
}
```

#### 讓某個 `refresh token` 立刻失效

```
DELETE /_security/oauth2/token
{
  "refresh_token" : "vLBPvmAB6KvwvJZr27cS"
}
```

#### 讓某個使用者的 tokens 立刻失效

```
DELETE /_security/oauth2/token
{
  "username" : "myuser"
}
```

依照官方的範例，回傳結果如下，會告訴我們總共 invalidate 多少個 tokens，以及若遇到錯誤各自的原因為何：

```
{
  "invalidated_tokens":9, 
  "previously_invalidated_tokens":15, 
  "error_count":2, 
  "error_details":[ 
    {
      "type":"exception",
      "reason":"Elasticsearch exception [type=exception, reason=foo]",
      "caused_by":{
        "type":"exception",
        "reason":"Elasticsearch exception [type=illegal_argument_exception, reason=bar]"
      }
    },
    {
      "type":"exception",
      "reason":"Elasticsearch exception [type=exception, reason=boo]",
      "caused_by":{
        "type":"exception",
        "reason":"Elasticsearch exception [type=illegal_argument_exception, reason=far]"
      }
    }
  ]
}
```

> \*\*小插曲：\*\*我在 Elastic Cloud service 試用這個 invlidate by username 時，發生以下的錯誤，看來是踩到某個 bug 了，看到 Github 上好像已經有類似的 issues，相信在不久後的 release 應該就會修掉了吧。
>
> <img src="https://i.imgur.com/rj3dDzq.png" alt="image-20201005054248853" data-size="original">

## 結語

以上是 Elasticsearch Token Service 的介紹，在 Micro-services 的架構之下，若是將 Elasticsearch 當成其中一個微服務來使用時，透過 Token based 的方式來整合與使用其資源，是個蠻好的資源管理方式，這個機制只要在 Elastic Cloud 中就能直接使用，若是 Self-managed 的版本，會需要購買 **Gold License** 才能使用，或是要走向其他 [Open Source 的 solution](https://opendistro.github.io/for-elasticsearch-docs/docs/security/)了。

## 參考資料

* [官方文件 - Token-based authentcation services](https://www.elastic.co/guide/en/elasticsearch/reference/master/token-authentication-services.html)
* [官方文件 - Get token API](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html)
* [官方文件 - Invalidate token API](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api-invalidate-token.html)
* [官方文件 - Security Settings - Token service settings](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-settings.html#token-service-settings)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://training.onedoggo.com/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/elasticsearch-token-service.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
