構成と使い方
スクリプト概要
| 機能 | パラメータセット |
|---|---|
| JSONファイルで設定変更(変更前後の状態表示付き) | -ConfigFile |
| 全体コンフィグ取得(テキスト形式) | -GetConfig |
| 全体コンフィグ取得(JSON形式) | -GetConfigJson |
実行例
powershell
# ① 設定変更(APIトークン推奨)
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxxxxx" `
-ConfigFile .\fgt-changes-sample.json
# ② テキストコンフィグ取得 → ファイル保存
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxxxxx" `
-GetConfig -OutputFile .\backup.conf
# ③ JSONコンフィグ取得 → ファイル保存
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxxxxx" `
-GetConfigJson -OutputFile .\backup.json
# ④ ユーザー/パスワード認証 + 自己署名証明書スキップ
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -Username admin -Password "P@ssw0rd" `
-ConfigFile .\changes.json -SkipCertCheck
```
---
### 設定JSONファイルの書き方
| フィールド | 必須 | 説明 |
|-----------|------|------|
| `description` | 任意 | 変更セットの説明 |
| `onError` | 任意 | `continue`(継続)/ `stop`(中断) グローバルデフォルト |
| `changes[].endpoint` | **必須** | APIエンドポイント(例: `cmdb/firewall/address`) |
| `changes[].method` | **必須** | `GET` / `POST` / `PUT` / `DELETE` |
| `changes[].name` | 任意 | 対象オブジェクト名(PUT/DELETE時に必要) |
| `changes[].data` | 任意 | 設定データ(POST/PUT時) |
| `changes[].onError` | 任意 | 個別エラー時の動作(グローバルを上書き) |
---
### APIトークンの作成方法
```
FGT管理GUI → System → Administrators → Create New → REST API Admin
→ Profile: super_admin(または必要な権限)
→ 生成されたトークンをコピー注意点
-GetConfigは/api/v2/monitor/system/config/backupを使用(FortiOS 6.4以降対応)-GetConfigJsonは主要 cmdb カテゴリを個別取得して結合。環境によって不要なカテゴリはスクリプト内の$categories配列から削除可能- VDOM環境では
-Vdomで対象VDOMを指定(デフォルト:root) - 証明書エラーが出る場合は
-SkipCertCheckを追加
---
{
"description": "サンプル設定変更セット - 2025-03-24",
"onError": "continue",
"changes": [
{
"description": "ファイアウォールアドレス追加(新規作成)",
"endpoint": "cmdb/firewall/address",
"method": "POST",
"name": "",
"onError": "continue",
"data": {
"name": "HOST_192.168.100.10",
"type": "ipmask",
"subnet": "192.168.100.10/32",
"comment": "サンプルホストアドレス"
}
},
{
"description": "ファイアウォールアドレス更新(既存変更)",
"endpoint": "cmdb/firewall/address",
"method": "PUT",
"name": "HOST_192.168.100.10",
"onError": "continue",
"data": {
"name": "HOST_192.168.100.10",
"type": "ipmask",
"subnet": "192.168.100.10/32",
"comment": "更新済みホストアドレス"
}
},
{
"description": "スタティックルート追加",
"endpoint": "cmdb/router/static",
"method": "POST",
"name": "",
"onError": "stop",
"data": {
"dst": "10.0.0.0/8",
"gateway": "192.168.1.254",
"device": "port1",
"comment": "内部ネットワーク向けルート"
}
},
{
"description": "DNS設定変更",
"endpoint": "cmdb/system/dns",
"method": "PUT",
"name": "",
"onError": "continue",
"data": {
"primary": "8.8.8.8",
"secondary": "8.8.4.4"
}
},
{
"description": "管理者パスワードポリシー変更",
"endpoint": "cmdb/system/password-policy",
"method": "PUT",
"name": "",
"onError": "continue",
"data": {
"status": "enable",
"min-length": 8,
"must-contain": "upper-case-letter lower-case-letter number"
}
},
{
"description": "不要なアドレスオブジェクトを削除",
"endpoint": "cmdb/firewall/address",
"method": "DELETE",
"name": "OLD_ADDRESS_TO_DELETE",
"onError": "continue",
"data": null
}
]
}
---#!/usr/bin/env pwsh
<#
.SYNOPSIS
FortiGate (FGT) REST API 設定管理スクリプト
.DESCRIPTION
FortiGate の REST API を使用して設定変更・取得を行う。
- JSONファイルで指定した設定を適用(変更前に現状取得)
- 全体コンフィグをテキスト形式で取得
- 全体コンフィグをJSON形式で取得
.PARAMETER ConfigFile
適用する設定を記述したJSONファイルのパス
.PARAMETER GetConfig
全体コンフィグをテキスト形式で取得
.PARAMETER GetConfigJson
全体コンフィグをJSON形式で取得
.PARAMETER FGTHost
FortiGate のIPアドレスまたはホスト名
.PARAMETER Port
HTTPS ポート番号(デフォルト: 443)
.PARAMETER ApiToken
REST API トークン(推奨)
.PARAMETER Username
ログインユーザー名(ApiToken未指定時)
.PARAMETER Password
ログインパスワード(ApiToken未指定時)
.PARAMETER Vdom
対象 VDOM(デフォルト: root)
.PARAMETER SkipCertCheck
TLS証明書の検証をスキップ(自己署名証明書対応)
.PARAMETER OutputFile
出力ファイルパス(省略時は標準出力のみ)
.PARAMETER ShowBefore
設定変更前の現状を表示する(デフォルト: $true)
.EXAMPLE
# 設定変更(APIトークン使用)
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxx" -ConfigFile .\changes.json
.EXAMPLE
# 全体コンフィグをテキストで取得しファイル保存
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxx" -GetConfig -OutputFile .\fgt.conf
.EXAMPLE
# 全体コンフィグをJSON形式で取得
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -ApiToken "xxxxx" -GetConfigJson -OutputFile .\fgt.json
.EXAMPLE
# ユーザー/パスワード認証で設定変更
.\Invoke-FGTConfig.ps1 -FGTHost 192.168.1.99 -Username admin -Password "pass" -ConfigFile .\changes.json -SkipCertCheck
.NOTES
PowerShell 7.5+ 必須
FortiOS 6.4 / 7.x 対応
#>
#Requires -Version 7.0
[CmdletBinding(DefaultParameterSetName = 'ApplyConfig')]
param(
# ---- モード選択 ----
[Parameter(ParameterSetName = 'ApplyConfig', Mandatory = $true, HelpMessage = '設定JSONファイルのパス')]
[ValidateScript({ Test-Path $_ -PathType Leaf })]
[string]$ConfigFile,
[Parameter(ParameterSetName = 'GetConfig', Mandatory = $true)]
[switch]$GetConfig,
[Parameter(ParameterSetName = 'GetConfigJson', Mandatory = $true)]
[switch]$GetConfigJson,
# ---- 接続情報 ----
[Parameter(Mandatory = $true, HelpMessage = 'FGT IPまたはホスト名')]
[string]$FGTHost,
[Parameter(Mandatory = $false)]
[ValidateRange(1, 65535)]
[int]$Port = 443,
[Parameter(Mandatory = $false)]
[string]$ApiToken,
[Parameter(Mandatory = $false)]
[string]$Username,
[Parameter(Mandatory = $false)]
[string]$Password,
[Parameter(Mandatory = $false)]
[string]$Vdom = 'root',
# ---- オプション ----
[Parameter(Mandatory = $false)]
[switch]$SkipCertCheck,
[Parameter(Mandatory = $false)]
[string]$OutputFile,
[Parameter(ParameterSetName = 'ApplyConfig', Mandatory = $false)]
[bool]$ShowBefore = $true
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# ============================================================
# 定数・グローバル変数
# ============================================================
$script:BaseUrl = "https://${FGTHost}:${Port}/api/v2"
$script:Headers = @{ 'Content-Type' = 'application/json' }
$script:SessionCsrf = $null
$script:SessionCookie = $null
# ============================================================
# ログ出力ヘルパー
# ============================================================
function Write-Log {
param(
[string]$Message,
[ValidateSet('INFO','SUCCESS','WARN','ERROR','SECTION')]
[string]$Level = 'INFO'
)
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$color = switch ($Level) {
'SUCCESS' { 'Green' }
'WARN' { 'Yellow' }
'ERROR' { 'Red' }
'SECTION' { 'Cyan' }
default { 'White' }
}
$prefix = switch ($Level) {
'SUCCESS' { '[OK] ' }
'WARN' { '[!!] ' }
'ERROR' { '[NG] ' }
'SECTION' { '=== ' }
default { '[--] ' }
}
Write-Host "${ts} ${prefix}${Message}" -ForegroundColor $color
}
# ============================================================
# TLS 証明書スキップ設定
# ============================================================
function Set-TlsPolicy {
if ($SkipCertCheck) {
Write-Log 'TLS証明書の検証をスキップします' -Level WARN
# PowerShell 7 では -SkipCertificateCheck パラメータを使用するため
# ここでは HttpClientHandler の設定は不要
}
}
# ============================================================
# Invoke-RestMethod ラッパー(共通ヘッダ・エラー処理)
# ============================================================
function Invoke-FGTApi {
param(
[string]$Uri,
[string]$Method = 'GET',
[hashtable]$Headers = @{},
[object]$Body = $null,
[switch]$RawResponse
)
$params = @{
Uri = $Uri
Method = $Method
Headers = $script:Headers + $Headers
}
if ($SkipCertCheck) {
$params['SkipCertificateCheck'] = $true
}
if ($null -ne $Body) {
$params['Body'] = ($Body | ConvertTo-Json -Depth 20 -Compress)
}
# セッションCookieがある場合はWebSession経由
if ($null -ne $script:SessionCookie) {
$params['WebSession'] = $script:SessionCookie
}
try {
if ($RawResponse) {
$params['ResponseHeadersVariable'] = 'respHeaders'
$result = Invoke-RestMethod @params
return @{ Body = $result; Headers = $respHeaders }
}
return Invoke-RestMethod @params
}
catch {
$statusCode = $_.Exception.Response?.StatusCode?.value__
$errBody = $null
try {
$errBody = $_.ErrorDetails.Message | ConvertFrom-Json
} catch { }
$errMsg = if ($errBody?.error) { $errBody.error } else { $_.Exception.Message }
throw "API エラー [HTTP $statusCode] ${Method} ${Uri} : ${errMsg}"
}
}
# ============================================================
# 認証
# ============================================================
function Connect-FGT {
Write-Log '=== FortiGate 接続中 ===' -Level SECTION
if ($ApiToken) {
# APIトークン認証(推奨)
$script:Headers['Authorization'] = "Bearer $ApiToken"
Write-Log "APIトークン認証: ${FGTHost}:${Port}" -Level INFO
}
elseif ($Username -and $Password) {
# ユーザー/パスワード認証(セッション)
Write-Log "ユーザー認証: ${Username}@${FGTHost}:${Port}" -Level INFO
$loginUri = "https://${FGTHost}:${Port}/logincheck"
$loginBody = "username=${Username}&secretkey=${Password}&ajax=1"
$webSession = $null
$loginParams = @{
Uri = $loginUri
Method = 'POST'
Body = $loginBody
ContentType = 'application/x-www-form-urlencoded'
SessionVariable = 'webSession'
}
if ($SkipCertCheck) { $loginParams['SkipCertificateCheck'] = $true }
$resp = Invoke-RestMethod @loginParams
if ($resp -ne '0' -and $resp -notmatch '"loginstatus":1') {
throw "ログイン失敗: レスポンス = $resp"
}
$script:SessionCookie = $webSession
# CSRF トークン取得
$csrfCookie = $webSession.Cookies.GetCookies("https://${FGTHost}")['ccsrftoken']
if ($csrfCookie) {
$csrfVal = $csrfCookie.Value -replace '"', ''
$script:Headers['X-CSRFTOKEN'] = $csrfVal
Write-Log "CSRFトークン取得: ${csrfVal}" -Level INFO
}
}
else {
throw '認証情報が不足しています。-ApiToken または -Username/-Password を指定してください。'
}
Write-Log '接続情報設定完了' -Level SUCCESS
}
# ============================================================
# ログアウト
# ============================================================
function Disconnect-FGT {
if ($null -ne $script:SessionCookie -and -not $ApiToken) {
try {
$logoutUri = "https://${FGTHost}:${Port}/logout"
$p = @{ Uri = $logoutUri; Method = 'GET'; WebSession = $script:SessionCookie }
if ($SkipCertCheck) { $p['SkipCertificateCheck'] = $true }
Invoke-RestMethod @p | Out-Null
Write-Log 'ログアウト完了' -Level INFO
} catch {
Write-Log "ログアウト中にエラー: $_" -Level WARN
}
}
}
# ============================================================
# 現状取得(変更前確認)
# ============================================================
function Get-FGTCurrentState {
param(
[string]$Endpoint,
[string]$Name = ''
)
$uri = "$script:BaseUrl/${Endpoint}"
if ($Name) { $uri += "/${Name}" }
$uri += "?vdom=${Vdom}"
try {
$resp = Invoke-FGTApi -Uri $uri -Method 'GET'
return $resp
}
catch {
Write-Log "現状取得失敗 [${Endpoint}/$Name]: $_" -Level WARN
return $null
}
}
# ============================================================
# 設定適用(PUT / POST / DELETE)
# ============================================================
function Invoke-FGTChange {
param(
[string]$Endpoint,
[string]$Method,
[string]$Name = '',
[object]$Data = $null,
[hashtable]$QueryParams = @{}
)
$uri = "$script:BaseUrl/${Endpoint}"
if ($Name -and $Method -in @('PUT','DELETE','GET')) { $uri += "/${Name}" }
$query = @{ vdom = $Vdom } + $QueryParams
$queryStr = ($query.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join '&'
$uri += "?${queryStr}"
$resp = Invoke-FGTApi -Uri $uri -Method $Method -Body $Data
return $resp
}
# ============================================================
# 全体コンフィグ取得 (テキスト形式)
# ============================================================
function Get-FGTFullConfig {
Write-Log '=== 全体コンフィグ取得 (テキスト) ===' -Level SECTION
$uri = "$script:BaseUrl/monitor/system/config/backup?scope=global&vdom=${Vdom}"
Write-Log "URI: $uri" -Level INFO
$params = @{
Uri = $uri
Method = 'GET'
Headers = $script:Headers
}
if ($SkipCertCheck) { $params['SkipCertificateCheck'] = $true }
if ($null -ne $script:SessionCookie) { $params['WebSession'] = $script:SessionCookie }
# テキストとして取得
$configText = Invoke-RestMethod @params
Write-Log "コンフィグ取得完了 (文字数: $($configText.Length))" -Level SUCCESS
if ($OutputFile) {
$configText | Set-Content -Path $OutputFile -Encoding UTF8
Write-Log "ファイル保存: $OutputFile" -Level SUCCESS
}
else {
Write-Output $configText
}
}
# ============================================================
# 全体コンフィグ取得 (JSON形式)
# ============================================================
function Get-FGTFullConfigJson {
Write-Log '=== 全体コンフィグ取得 (JSON) ===' -Level SECTION
# cmdb エンドポイントへのアクセス(主要カテゴリを収集)
$categories = @(
'system/global',
'system/interface',
'system/dns',
'system/ntp',
'system/admin',
'firewall/address',
'firewall/addrgrp',
'firewall/service/custom',
'firewall/service/group',
'firewall/policy',
'firewall/vip',
'router/static',
'router/ospf',
'router/bgp',
'vpn/ipsec/phase1-interface',
'vpn/ipsec/phase2-interface',
'user/local',
'user/group'
)
$fullConfig = [ordered]@{
'_metadata' = @{
host = $FGTHost
vdom = $Vdom
retrieved_at = (Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz')
}
}
foreach ($cat in $categories) {
$uri = "$script:BaseUrl/cmdb/${cat}?vdom=${Vdom}"
Write-Log "取得中: $cat" -Level INFO
try {
$resp = Invoke-FGTApi -Uri $uri -Method 'GET'
$key = $cat -replace '/', '_'
$fullConfig[$key] = $resp.results ?? $resp
}
catch {
Write-Log "スキップ [${cat}]: $_" -Level WARN
$fullConfig[($cat -replace '/', '_')] = $null
}
}
$json = $fullConfig | ConvertTo-Json -Depth 20
Write-Log "JSON生成完了 (文字数: $($json.Length))" -Level SUCCESS
if ($OutputFile) {
$json | Set-Content -Path $OutputFile -Encoding UTF8
Write-Log "ファイル保存: $OutputFile" -Level SUCCESS
}
else {
Write-Output $json
}
}
# ============================================================
# 設定変更 (JSONファイル読み込み)
# ============================================================
function Invoke-FGTConfigFile {
param([string]$FilePath)
Write-Log "=== 設定変更開始: $FilePath ===" -Level SECTION
# JSON 読み込み
$raw = Get-Content -Path $FilePath -Raw -Encoding UTF8
$config = $raw | ConvertFrom-Json -AsHashtable
$description = $config['description'] ?? '(説明なし)'
Write-Log "変更セット: $description" -Level INFO
$changes = $config['changes']
if (-not $changes -or $changes.Count -eq 0) {
Write-Log 'changes が空です。処理を終了します。' -Level WARN
return
}
$total = $changes.Count
$success = 0
$failed = 0
foreach ($i in 0..($total - 1)) {
$change = $changes[$i]
$idx = $i + 1
$desc = $change['description'] ?? "(変更 $idx)"
$ep = $change['endpoint']
$method = ($change['method'] ?? 'PUT').ToUpper()
$name = $change['name'] ?? ''
$data = $change['data'] ?? $null
Write-Log "--- [$idx/$total] $desc ---" -Level SECTION
# 変更前の現状取得
if ($ShowBefore -and $method -ne 'DELETE') {
Write-Log "変更前の現状取得: ${ep}/$name" -Level INFO
$before = Get-FGTCurrentState -Endpoint $ep -Name $name
if ($before) {
$beforeJson = $before | ConvertTo-Json -Depth 10
Write-Log '【変更前】' -Level INFO
Write-Host $beforeJson -ForegroundColor DarkGray
}
else {
Write-Log '現状データなし(新規作成の可能性)' -Level INFO
}
}
# 設定変更実行
Write-Log "${method} ${ep}/$name を実行中..." -Level INFO
try {
$result = Invoke-FGTChange -Endpoint $ep -Method $method -Name $name -Data $data
$httpStatus = $result.http_status ?? $result.status ?? 'unknown'
Write-Log "完了 [status=$httpStatus]: $desc" -Level SUCCESS
# 変更後の確認取得
if ($method -ne 'DELETE') {
$afterName = $name ?: ($data?['name'] ?? '')
$after = Get-FGTCurrentState -Endpoint $ep -Name $afterName
if ($after) {
$afterJson = $after | ConvertTo-Json -Depth 10
Write-Log '【変更後】' -Level INFO
Write-Host $afterJson -ForegroundColor Green
}
}
$success++
}
catch {
Write-Log "失敗: $desc | $_" -Level ERROR
$failed++
# エラー時の動作(continue or stop)
$onError = $change['onError'] ?? $config['onError'] ?? 'continue'
if ($onError -eq 'stop') {
Write-Log 'onError=stop のため処理を中断します。' -Level ERROR
break
}
}
}
Write-Log "=== 処理完了: 成功=$success / 失敗=$failed / 合計=$total ===" -Level SECTION
}
# ============================================================
# メイン処理
# ============================================================
function Main {
Set-TlsPolicy
# 接続
Connect-FGT
try {
switch ($PSCmdlet.ParameterSetName) {
'ApplyConfig' { Invoke-FGTConfigFile -FilePath $ConfigFile }
'GetConfig' { Get-FGTFullConfig }
'GetConfigJson'{ Get-FGTFullConfigJson }
}
}
finally {
Disconnect-FGT
}
}
# エントリポイント
Main