Blog Cover Image

Inspire you to have New thinking, Walk out your unique Road.

有的時候,你無意間遇到的一些故事,會激發你的靈感,改變你的想法,接下來你會用與之前全然不同的觀念去創造屬於你獨特的故事。

Sign @MinaYu.

[後端技術研究] Black Duck APIs 和 Fortify On Demand APIs 怎麼使用? (透過Python Requests或Postman)

Posted on

公司有一項冗長繁瑣的程式資安審核流程使用Black Duck和Fortify On Demand Scans,為了能達成自動化兩個掃描功能,我們研究了這兩項掃描服務的APIs,並在此文章紀錄我們的研究成果。

研究 Black Duck(BD)Fortify On Demand(FoD) 掃描服務,是由於公司希望能將這兩項掃描服務自動化,在經過3週左右的研究,我成功的運用APIs達成我們所需的功能,將研究結果記錄於此篇。

請注意,此篇紀錄的API endpoints僅有達成我們功能所需的APIs,並非所有。

因為我當初在研究時,發現網路上幾乎沒有其他人分享怎麼使用這兩個掃描服務的APIs,所以如果你幸運需要研究如何自動化或者如何使用APIs,希望這篇文章能幫助到你。

原則上,兩項掃描服務的APIs 文件都寫得還算詳細,但還是會遇到一些小問題,所以我會把那些遇到的坑寫出來。

大綱

以下是本篇文章介紹的APIs大綱

掃描服務 功能 描述 API
Fortify On Demand Oauth Authentication 取得授權的Oauth token,才能使用這個token call 其他 APIs https://api.ams.fortify.com/oauth/token
Get Application 取得所有的applications https://api.ams.fortify.com/api/v3/application
Get Release (with filter) 取得 release或隸屬於指定的application下的release,包含講解filter查找功能 https://api.ams.fortify.com/api/v3/releasehttps://api.ams.fortify.com/api/v3/application/<application_id>/release
Black Duck Authentication 取得用來call其他API的bearer token https://<host_name>/api/tokens/authenticate
Query project 找到指定的projct (含filter過濾) https://<host_name>/api/projects
Get version 取得指定的project下所有的版本資訊 https://<host_name>/api/projects/<project_id>/versions
Generate Version Details Report 對於指定的project及版本產出Version Details報告 https://<host_name>/api/versions/<version_id>/reports
Download Version Details Report 下載報告 https://<host_name>/api/projects/<project_id>/versions/<version_id>/reports/<report_id>/download
Visit components with filter 針對指定的project 的components 加上條件式篩選 https://<host_name>/api/projects/<project_id>/versions/<version_id>/components

Fortify On Demand(FoD)

Fortify On Demand(以下簡稱FoD),擁有公開的官方Swagger文件,提供你各式各樣的APIs使用,他還有一個很便利的功能就是可以直接用此Swagger文件模擬並發送請求至APIs,所以當Call各種APIs都回傳Error時,可以考慮直接用這個模擬器先試試看。

今天介紹以下API Endpoints:

  • Oauth Authentication: https://api.ams.fortify.com/oauth/token
  • Get Application: https://api.ams.fortify.com/api/v3/application
  • Get Release (with filter): https://api.ams.fortify.com/api/v3/release or https://api.ams.fortify.com/api/v3/application/<application_id>/release

Oauth Authentication

在Call任何FoD的APIs前,你必須請求這個Oauth API Endpoint,取得回傳的token,並使用那個token 請求其他FoD的API。

// POST https://api.ams.fortify.com/oauth/token
// Header
{
    "scope": "api-tenant", // scope 要去你的帳號設定查看。
    "grant_type": "password", // 取得權限是用password形式。
    "username": "<tenant>\\<user_name>", // tenant跟user name也是要去帳號設定查看,記得注意一個反斜線 \,但若是在程式碼內需要兩個反斜線\\,防止程式辨別特數字元。
    "password": "<personal_token>" // 注意此項不是放你的密碼,是放你在設定裡產生的那組token。
}

response

{
    "access_token": "ZGHJ3274JDK" //送出請求後,可從response獲得一組token,這邊只是隨便寫代表,實際上很長。
}

Get Application

拿到專門call API的token後,我們就能使用那組token請求其他的API功能,首先我們先取得 Applications,這組APIs會取得列表Application。

// GET https://api.ams.fortify.com/api/v3/application
// Header
{
    "Content-Type": "application/json", 
    "Accept": "application/json", 
    "Authorization": "Bearer <token>", // 這邊要放你剛剛call Oauth取得的token
}

response

{
    ...,
    "items": [{
        "applicationName": "",
        "applicationId": "",
        ...
    }, {
        ...
    }
    ]
}

透過此APIs,你會取得多個Application,你可以知道他們的Name及applicationId,方便查詢其他API使用,此API支援filter,可以直接查找你想要找的Application,我會在下個API介紹,filter用法都差不多。


Get Release (with filter)

你可以選擇一次獲取全部的release或者獲取指定application下的release,後者需要applicationId。

// GET https://api.ams.fortify.com/api/v3/release or https://api.ams.fortify.com/api/v3/application/<application_id>/release
// Header
{
    "Content-Type": "application/json", 
    "Accept": "application/json", 
    "Authorization": "Bearer <token>", // 這邊要放你剛剛call Oauth取得的token
}

// Params -> 注意是Parameters,不是Body,這是Get請求,Python請放requests.get(params=<params>),postman請把參數放在Params,我被這坑到。
{
    "filter": "ReleaseName:<service_name>" // 這是單一filter案例,多個請用 + 分開
}

這邊要特別注意filter放個內容是 <你要filter的欄位名稱1>:<你要filter的值1>+<你要filter的欄位名稱2>:<你要filter的值2>,所以假設我今天想要filter ReleaseNameppt-scanner,就放ReleaseName:ppt-scanner,然後請夾帶至請求的Parameters欄位,這是Get方法。

至於能filter什麼,文件上好像沒寫,但可以仔細觀察release API 回傳的Response會列出所有Release的服務,會順帶回傳該Release的所有屬性(資訊),你可以去推哪些屬性是可以被拿來當作Filter的欄位。


Black Duck (BD)

Black Duck (以下簡稱BD),其APIs文件為非公開,你可以去網頁右上方的?REST API Developer Guide,BD的API文件,我個人覺得寫得很詳細,不過還是會有些需要注意的地方,但已經很完整了。

今天介紹以下BD API Endpoints,由於Black Duck APIs是公司自架,自己的網域,所以以下只會列出後面的API連結,實際請求時,你要將host_name換成你們的網域:

  • Authentication: https://<host_name>/api/tokens/authenticate
  • Query project: https://<host_name>/api/projects
  • Get version: https://<host_name>/api/projects/<project_id>/versions
  • Generate Version Details Report: https://<host_name>/api/versions/<version_id>/reports
  • Download Version Details Report: https://<host_name>/api/projects/<project_id>/versions/<version_id>/reports/<report_id>/download
  • Visit components with filter: https://<host_name>/api/projects/<project_id>/versions/<version_id>/components

Authentication

跟上方的FoD一樣,在Call任何其他BD的APIs前,你必須請求這個authenticate API Endpoint,取得回傳的token,並使用那個token 請求其他BD的API。

// POST https://<host_name>/api/tokens/authenticate
// Header
{
    "Accept": "application/vnd.blackducksoftware.user-4+json", 
    "Authorization": "token <personal_token>", // 你可以在Black Duck的網站右上方人頭 -> Access token 產生一個 personal token,然後注意如果你需要產報告,你要把write權限打勾。
    // Authorization的值,請拜託一定要在前面加 token,才把personal token寫下去,我當時卡這個卡的一臉懵,卡一陣子,例子: "Authorization": "token GH2395JKls=="
}

response

{
    "bearerToken": "ZGHJ3274JDK" //送出請求後,可從response獲得一組token,這邊只是隨便寫代表,實際上很長。
}

Query project

拿到專門call API的token後,我們就能使用那組token請求其他的API功能,這個API可以取得所有的projects,但你可以請API找出你想找的project filter。

// GET https://<host_name>/api/projects
// Header
{
    "Authorization": "Bearer <bearerToken>", // 這邊要放你剛剛取得的BearerToken
}
// Params -> 沒錯又是Parameters,這是get請求,不要放去body。
// 如果你需要 limit限制回傳的筆數或者filter過濾你要找的projects,才需要這個parameters,如果沒有params,會回傳全部的projects (有限制筆數)
{
    "limit": 5, // 我限制5筆就好
    "q": "name:<filter_name>" // 沒錯就是一個 q,這欄是filter,如果你想查指定名稱的project,就照這樣填,不確定其他可以filter的欄位。
}

response

{
    ...,
    "items": [{
        "name": "",
        "_meta": {
            "href": ""
        },
        ...
    }, {
        ...
    }
    ]
}

我發現就是呢,回傳的project資訊好像沒有project_id,所以我當初是在project_metahref連結這個欄位,然後split("/")[-1] 取最後一段id,那是該project的id。


Get version

當你在上一步驟取得project後,可以使用這個api取得所有隸屬於那個project的版本versions。

// GET https://<host_name>/api/projects/<project_id>/versions
// Header
{
    "Authorization": "Bearer <bearerToken>", // 這邊要放你剛剛取得的BearerToken
}

記得在網址放上指定的project_id

response

{
    "items": [{
        "versionName": "master",
        ...,
        "_meta": {
            "href": ""
        },
    },
    {
        "versionName": "version_1",
        ...,
        "_meta": {
            "href": ""
        },
    }
    ]
}

沒錯,就是呢,他又不會回傳version_id了,我們又得去_meta裡的href切割網址並找出他的version_id。

split("/")[-1],一樣取最後一個,回傳的網址應該會長得像 https://<host_name>/api/projects/<project_id>/versions/<version_id>


Generate Version Details Report

現在我們有 project_idversion_id 了,那我們可以產出報告了。我只有研究怎麼產生 Version Details 報告,其他三種不太理解,但API寫得很清楚。

// POST https://<host_name>/api/versions/<version_id>/reports
// 沒錯很奇怪,居然不需要project_id,我懷疑他萬一有重複的version_id會怎麼判斷。
// Header
{
    "Content-Type": "application/json",
    "Authorization": "Bearer <bearerToken>", // 這邊要放你剛剛取得的BearerToken
}

// Body -> 終於是 body了,post請求哈。
{
    "reportFormat": "CSV", // 這邊我們選csv,但我記得可以填JSON
    "reportType": "VERSION",
    "caregories": ["VERSION", "COMPONENTS", "SECURITY", "PROJECT_VERSION_CUSTOM_FIELDS"] // 這邊就是你產報告時,他會有Categories 的打勾勾選項,你選哪些,這邊就填哪些。
}

如果categories的部分你不是很確定的話,你可以去網站實際產一份報告,打勾你要的欄位,然後打開開發者工具-> Network,點選產生報告,Network會紀錄他call了哪個API,然後你可以去看他的Body送了什麼,複製過來就行。

response

{
    "links": {
        "download": {
            "url"
        }
    }
}

Reponse的格式我不太確定,因為我是在python寫,回傳的好像是實際一個物件,你可以從links -> download -> url 去取得 report_id

又是老招 split("/")[-1],對 url 取最後一段,那就是report_id

我在想搞不好其實都能獲得 project_id, version_id, report_id,只是我文件沒看清楚。


Download Version Details Report

產生報告後,記住他不是馬上完成,所以如果你是撰寫程式自動化,記得要寫while迴圈每7-10秒call一次 (沒意外大概30秒內都能完成)。

如果報告沒完成會回傳 HTTP Status 412,但注意的是不只報告沒產好會回傳412,其他錯誤也會回傳412,要注意看回傳412額外的message是否指報告還沒產好。

上一步驟產生的報告,這步驟我們就下載下來。

// GET https://<host_name>/api/projects/<project_id>/versions/<version_id>/reports/<report_id>/download
// 真奇妙,產報告不需要 project_id,載報告需要,難道report_id是會重複的?
// Header
{
    "Authorization": "Bearer <bearerToken>", // 這邊要放你剛剛取得的BearerToken
}

一條get就把內容載下來了,response 的content是 bytes檔,我不知道怎麼用json呈現,以下是展示怎麼把回傳的bytes文字存擋。 (用python)

# 如果是下載在local
import io
import zipfile

response = requests.get(url, headers=headers)
if response.status_code == 200:
    z = zipfile.ZipFile(io.BytesIO(response.content))
    z.extractall("./")
# 如果是想存進 AWS S3
import io
import boto3
import zipfile

s3_client = boto3.client("s3")

s3_bucket = "<bucket_name>"

response = requests.get(url, headers=headers)
if response.status_code == 200:
    z = zipfile.ZipFile(io.BytesIO(response.content))
    for file_info in z.infolist():
        file_name = file_info.filename

        # 讀取 file content
        file_content = z.read(file_name)

        save_key = f"<prefix>/{file_name}"

        # 上傳檔案內容到S3
        s3_client.upload_fileobj(io.BytesIO(file_content), s3_bucket, save_key)

Visit components with filter

最後一個要介紹的是,訪問指定的project_id與指定版本version_id的components,並添加filter,去過濾components。

此API,我只有嘗試加一個filter,文件裡有寫可以加多個filters。

// GET https://<host_name>/api/projects/<project_id>/versions/<version_id>/components
// 一樣要填 project_id 和 version_id

// Header
{
    "Authorization": "Bearer <bearerToken>", // 這邊要放你剛剛取得的BearerToken
}

// Params -> Parameters 哈,這邊用GET方式
{
    "limit": 10, // 看你要不要設筆數
    "filter": "bomPolicy:in_violation" // filter -> 欄位:選項
}

這邊抓出有 violation 的 components。

response

// 如果沒有找到filter符合的資料,items會留空 []。
{
    "items": [{
        "componentName": "",
        "componentVersionName": ""
    },
    {
        "componentName": "",
        "componentVersionName": ""
    }
    ]
}

如此一來就能透過添加 Params 的方式,去filter components。

那如果你不確定 filter內容怎麼寫,一樣可以直接去網站,進到components,打開開發者工具 -> Network,然後添加filters,你就會看到Network抓到的Params資訊。

或者可以透過網址直接辨識: https://<host_name>/api/projects/<project_id>/versions/<version_id>/components?filter=bomPolicy:in_violation,在?filter=後面的值辨識那些欄位跟植,然後寫進請求裡的Params裡嘗試。


好啦,以上就是此次 Fortify On Demand 和 Black Duck APIs的研究結果,希望有幫到你。