PowerShell で ISE の ERS API をたたく

# はじめに

手軽に API を操作したい場合、cURL や Postman、各種プログラミング言語で用意されている HTTP Client などを利用することが多いと思いますが、様々な制約によってそのような環境を用意できないこともあるのではないでしょうか。

この投稿では、Windows 端末（PowerShell）さえあれば最低限 REST API を操作できるということを Cisco Identity Services Engine （以下 ISE）の API を題材に紹介します。

*PowerShell ISE（Integrated Scripting Environment）と紛らわしいですが関係ありません。

いずれも PowerShell でデフォルトで利用できる `Invoke-WebRequest` コマンドを利用します。参考のため、併せて同等の操作を Python で実装した例も記載します。

# 環境
・Windows 10 Pro
・PowerShell version 5.1（Windows インストール後デフォルト設定のまま）
・Cisco ISE version 2.6

# 準備

[Qiita の記事](https://qiita.com/naixia/items/5c521183c2b606a891b1)もしくは[DevNet の記事](https://developer.cisco.com/docs/identity-services-engine/)を参照ください。


# やりたいこと① GET による情報取得
## PowerShell 実装例
```powershell

# SSL エラー回避のための処理
add-type @"
  using System.Net;
  using System.Security.Cryptography.X509Certificates;
  public class TrustAllCertsPolicy : ICertificatePolicy {
      public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate,
                                        WebRequest request, int certificateProblem) {
          return true;
      }
   }
"@
 
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy


# 変数の定義
$username = 'ersadmin' # ERS Admin Username
$password = 'XXXX' # ERS Admin Password
$url = 'https://X.X.X.X:9060/ers/config/internaluser/' # X.X.X.X => ISE's IP address
$credPair = "$($username):$($password)"
 
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($credPair))
 hu
$headers = @{
'Authorization'= "Basic $encodedCredentials"; 
'Accept'= 'application/json'; 
'cache-control'= 'no-cache'
 }

# API コール
$responseData = Invoke-WebRequest -Uri $url -Method Get -Headers $headers -UseBasicParsing

# 確認方法の例 
return $responseData
return $responseData.StatusCode
return $responseData.Header
return $responseData.RawContent
return $responseData.Content
```

PowerShell にはデフォルトで HTTP クライアントとして `Invoke-WebRequest` のほかに `Invoke-RestMethod` というコマンドが用意されています。
今回のようにレスポンスのヘッダやコンテンツをまずは人が見やすい形で表示するには `Invoke-WebRequest` に `-UserBasicParsing` をつける方が向いていて、返り値を直接扱ってなにかしたい場合は `Invoke-RestMethod` が便利そうです。
[こちら](https://qiita.com/startPG/items/7b37c527667fa43ab173)と[こちら](https://www.it-swarm.dev/ja/windows/invokewebrequestとinvokerestmethodの違いは何ですか？/944432281/)も参考になります。

## 参考 Python 実装例
python3.7 (mac) で API コールに Requests ライブラリを利用 (こちらのほうがシンプルですね)

```python

import requests
import json
import base64

host = "X.X.X.X" # ISE's IP address
user = "ersadmin" # ERS Admin Username
password = "XXXXXX" # ERS Admin Password

creds = str.encode(':'.join((user, password)))
encodedAuth = bytes.decode(base64.b64encode(creds))

headers = {
    'accept': "application/json",
    'authorization': " ".join(("Basic",encodedAuth)),
    'cache-control': "no-cache",
    }

url = "https://{}:9060".format(host) + "/ers/config/internaluser/"

r = requests.get(url, headers=headers,verify=False) # 今回 ISE が自己署名証明書を利用しているため SSL エラー回避オプションを有効化

data = r.json()

print(json.dumps(data, indent=4))
```


# やりたいこと② PUT による情報更新
## PowerShell 実装例

```powershell

# SSL エラー回避のための処理
add-type @"
  using System.Net;
  using System.Security.Cryptography.X509Certificates;
  public class TrustAllCertsPolicy : ICertificatePolicy {
      public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate,
                                        WebRequest request, int certificateProblem) {
          return true;
      }
   }
"@
 
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy


# 変数の定義
$username = 'ersadmin' # ERS Admin Username
$password = 'XXXX' # ERS Admin Password
$url = 'https://X.X.X.X:9060/ers/config/internaluser/' + "57d1fada-3ab6-4d62-94eb-9b77be36dc7e"  # X.X.X.X => ISE's IP address + 対象ユーザの ID が URL となる

$credPair = "$($username):$($password)"
 
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($credPair))
 

# ヘッダに Content-type を追加
$headers = @{
'Authorization'= "Basic $encodedCredentials"; 
'Accept'= 'application/json'; 
'cache-control'= 'no-cache';
'content-type'= 'application/json'
 }

# ボディの定義。パラメータは変更したいアカウントの id, name, password が最低限必要
$body = @{
"InternalUser" = @{
"id" = "57d1fada-3ab6-4d62-94eb-9b77be36dc7e";
"name" = "user1";
"password"="Password123"
}
} | convertTo-Json

# API リクエスト
$responseData = Invoke-WebRequest -Uri $url -Method PUT -Headers $headers -Body $body -UseBasicParsing

# 確認方法
return $responseData.RawContent
return $responseData
return $responseData.StatusCode
return $responseData.Header
return $responseData.Content

```


## 参考 Python 実装例

```python

import requests
import json
import base64

host = "X.X.X.X" # ISE's IP address
user = "ersadmin" # ERS Admin Username
password = "XXXXXX" # ERS Admin Password
user_id = "57d1fada-3ab6-4d62-94eb-9b77be36dc7e" # 更新対象ユーザの ID

creds = str.encode(':'.join((user, password)))
encodedAuth = bytes.decode(base64.b64encode(creds))

headers = {
    'accept': "application/json",
    'content-type': "application/json",
    'authorization': " ".join(("Basic",encodedAuth)),
    'cache-control': "no-cache",
    }

req_body_json = """  {{
    "InternalUser" : {{
        "id" : "{}",
        "name" : "user1",
        "password" : "Password123",
        "customAttributes" : {{
        }}
    }}
}}
""".format(user_id,user_name,new_passwd)


url = "https://{}:9060".format(host) + "/ers/config/internaluser/{}".format(id)

r = requests.put(url, headers=headers, data=req_body_json, verify=False) 

data = r.json()

print(json.dumps(data, indent=4))

```

# 参照
PowerShell における Basic 認証のやりかた
https://pallabpain.wordpress.com/2016/09/14/rest-api-call-with-basic-authentication-in-powershell/

DevNet Cisco ISE ERS API Reference Guide
https://developer.cisco.com/docs/identity-services-engine/

ERS API (External RESTful Services API) 
https://www.cisco.com/c/en/us/td/docs/security/ise/2-6/api_ref_guide/api_ref_book/ise_api_ref_ers1.html

Python初心者が、ISE ERS API を利用してスクリプトを書いてみた
https://qiita.com/naixia/items/5c521183c2b606a891b1

Invoke-WebRequestとInvoke-RestMethodの違いは何ですか？
https://www.it-swarm.dev/ja/windows/invokewebrequestとinvokerestmethodの違いは何ですか？/944432281/