Automated task processing with JIRA API

It’s no secret that task tracker is one of the most critical resources of any big organization. The whole working process can be represented as creating, processing and closing various tasks. Without task tracker there will be complete disaster, collapse and anarchy. So, it’s very important to work with this instrument efficiently. And when I write efficiently, I mean in automated way. It’s a blog about security automation after all. 😉

This post will be about Jira task tracker. I have already wrote how Jira can be used in VM Remediation process. That post was mainly about the main principles and how remediation tasks look in Jira WEB-GUI. Now, I will go further and show how to use it as a source of important information and easily deal with daily routine tasks using some trivial scripting. It is all possible because of advanced Jira Rest API.

Let’s say we have some regular tasks of some type. For example, to detect vulnerabilities on some hosts using Nessus and make a comment about founded vulnerabilities in the task. You can make a script that we will search for this kind of tasks in Jira, process them, add scan results to the comment and close the task. Of course it works the best when these tasks are also were created with in some automated way, in this case parsing will be much easier.

“Issue”, is the right name for the task in Jira; but I frequently use “issue”, “task” and “ticket” interchangeably. Sorry for this.

So, we need to take this steps:

  • Authorization
  • Search for existing Jira issues using some search request
  • View description, data and comments of the issue
  • Download files attached to the issue
  • Make some task processing
  • Add a new comment to the issue
  • Change status of the issue

Atlassian JIRA is a very well documented products. I have used this articles about IRA REST API:

Authorization

For authorization you can use your own login and password. Just use “-u jira_login:jira_password” in every curl request.

Searching for Jira issues

Jira supports advanced search request in a special JIRA Query Language (JQL). For example, to get tasks of some project and with some text in Summary field you can use this search request: “jql=project=SECSCAN AND Summary ~ \”Scan task 123\””

curl -u jira_login:jira_password -X GET --data-urlencode "jql=project=SECSCAN AND Summary ~ \"Scan task 123\"" -H "Content-Type: application/json" "https:///rest/api/2/search" | python -m json.tool

In response we receive a huge JSON structure:

{
    "expand": "names,schema",
    "issues": [
        {
            "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
            "fields": {
                "aggregateprogress": {
                    "progress": 0,
                    "total": 0
                },
                "aggregatetimeestimate": null,
                "aggregatetimeoriginalestimate": null,
                "aggregatetimespent": null,
                "assignee": {
                    "active": true,
                    "avatarUrls": {
                        "16x16": "https://[file_server]/avatar/47ce8937d79322e6a9b8c68e4798c0a9?d=mm&s=16",
                        "24x24": "https://[file_server]/avatar/47ce8937d79322e6a9b8c68e4798c0a9?d=mm&s=24",
                        "32x32": "https://[file_server]/avatar/47ce8937d79322e6a9b8c68e4798c0a9?d=mm&s=32",
                        "48x48": "https://[file_server]/avatar/47ce8937d79322e6a9b8c68e4798c0a9?d=mm&s=48"
                    },
                    "displayName": "Alexander Leonov",
                    "emailAddress": "aleonov@corporation.com",
                    "key": "aleonov@corporation.com",
                    "name": "aleonov@corporation.com",
                    "self": "https://[jira_server]/rest/api/2/user?username=aleonov%40corporation.com",
                    "timeZone": "Europe/Moscow"
                },
                "components": [],
                "created": "2017-01-01T08:00:04.000+0300",
                "creator": {
                    "active": true,
                    "avatarUrls": {
                        "16x16": "https://[jira_server]/secure/useravatar?size=xsmall&ownerId=secbot&avatarId=12322",
                        "24x24": "https://[jira_server]/secure/useravatar?size=small&ownerId=secbot&avatarId=12322",
                        "32x32": "https://[jira_server]/secure/useravatar?size=medium&ownerId=secbot&avatarId=12322",
                        "48x48": "https://[jira_server]/secure/useravatar?ownerId=secbot&avatarId=12322"
                    },
                    "displayName": "sec bot",
                    "emailAddress": "secbot@corporation.com",
                    "key": "secbot",
                    "name": "secbot",
                    "self": "https://[jira_server]/rest/api/2/user?username=secbot",
                    "timeZone": "Europe/Moscow"
                },
                "customfield_10040": null,
[...]
                "customfield_40606": null,
                "description": "Scan the hosts listed in attached file.",
                "duedate": null,
                "fixVersions": [],
                "issuelinks": [],
                "issuetype": {
                    "avatarId": 12422,
                    "description": "",
                    "iconUrl": "https://[jira_server]/secure/viewavatar?size=xsmall&avatarId=12422&avatarType=issuetype",
                    "id": "15322",
                    "name": "Access check",
                    "self": "https://[jira_server]/rest/api/2/issuetype/15322",
                    "subtask": false
                },
                "labels": [],
                "lastViewed": null,
                "priority": {
                    "iconUrl": "https://[jira_server]/images/icons/priorities/major.svg",
                    "id": "3",
                    "name": "PRIORITY NAME",
                    "self": "https://[jira_server]/rest/api/2/priority/3"
                },
                "progress": {
                    "progress": 0,
                    "total": 0
                },
                "project": {
                    "avatarUrls": {
                        "16x16": "https://[jira_server]/secure/projectavatar?size=xsmall&pid=10123&avatarId=10005",
                        "24x24": "https://[jira_server]/secure/projectavatar?size=small&pid=10123&avatarId=10005",
                        "32x32": "https://[jira_server]/secure/projectavatar?size=medium&pid=10123&avatarId=10005",
                        "48x48": "https://[jira_server]/secure/projectavatar?pid=10123&avatarId=10005"
                    },
                    "id": "10123",
                    "key": "SECSCAN",
                    "name": "SECURITY SCAN",
                    "self": "https://[jira_server]/rest/api/2/project/10123"
                },
                "reporter": {
                    "active": true,
                    "avatarUrls": {
                        "16x16": "https://[file_server]/avatar/e35cb040a2321352217b9d05af15e61?d=mm&s=16",
                        "24x24": "https://[file_server]/avatar/e35cb040a2321352217b9d05af15e61?d=mm&s=24",
                        "32x32": "https://[file_server]/avatar/e35cb040a2321352217b9d05af15e61?d=mm&s=32",
                        "48x48": "https://[file_server]/avatar/e35cb040a2321352217b9d05af15e61?d=mm&s=48"
                    },
                    "displayName": "sec bot",
                    "emailAddress": "secbot@corporation.com",
                    "key": "secbot",
                    "name": "secbot",
                    "self": "https://[jira_server]/rest/api/2/user?username=secbot",
                    "timeZone": "Europe/Moscow"
                },
                "resolution": null,
                "resolutiondate": null,
                "status": {
                    "description": "Task is ready to start work",
                    "iconUrl": "https://[jira_server]/images/icons/statuses/open.png",
                    "id": "1",
                    "name": "New",
                    "self": "https://[jira_server]/rest/api/2/status/1",
                    "statusCategory": {
                        "colorName": "blue-gray",
                        "id": 2,
                        "key": "new",
                        "name": "STATUS NAME",
                        "self": "https://[jira_server]/rest/api/2/statuscategory/2"
                    }
                },
                "subtasks": [],
                "summary": "Scan task 123",
                "timeestimate": null,
                "timeoriginalestimate": null,
                "timespent": null,
                "updated": "2017-01-09T11:44:43.000+0300",
                "versions": [],
                "votes": {
                    "hasVoted": false,
                    "self": "https://[jira_server]/rest/api/2/issue/SECSCAN-1244/votes",
                    "votes": 0
                },
                "watches": {
                    "isWatching": false,
                    "self": "https://[jira_server]/rest/api/2/issue/SECSCAN-1244/watchers",
                    "watchCount": 1
                },
                "workratio": -1
            },
            "id": "1833537",
            "key": "SECSCAN-1244",
            "self": "https://[jira_server]/rest/api/2/issue/1833537"
        }
    ],
    "maxResults": 50,
    "startAt": 0,
    "total": 1
}

In the bottom of the JSON response you can see total amount of the issues in the search results. Most information on the issue is available here: summary, description, issue id (key), who created this issue and who is processing it right now. It may be a good practice to search in this JSON response if we really found what we needed, because sometimes Jira may return some unnecessary results as well. Jira search engine can be too much intelligent. 😉

Issue Details

The only things that you won’t find in search response are attached files and commentaries. To get them you should request the issue by it’s id https://[jira_server]/rest/api/2/issue/1833537 (see url in “self”) or the key https://[jira_server]/rest/api/2/issue/SECSCAN-1244

curl -u jira_login:jira_password -X GET -H "Content-Type: application/json" "https://[jira_server]/rest/api/2/issue/SECSCAN-1244"

I will show here only new fields of response:

{
    "expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
    "fields": {
        "aggregateprogress": {
            "progress": 0,
            "total": 0
        },
        "aggregatetimeestimate": null,
        "aggregatetimeoriginalestimate": null,
        "aggregatetimespent": null,
...
        "attachment": [
            {
                "author": {
                    "active": true,
                    "avatarUrls": {
                        "16x16": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=16",
                        "24x24": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=24",
                        "32x32": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=32",
                        "48x48": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=48"
                    },
                    "displayName": "John Smith",
                    "emailAddress": "j.smith@corporation.com",
                    "key": "j.smith@corporation.com",
                    "name": "j.smith@corporation.com",
                    "self": "https://[jira_server]/rest/api/2/user?username=j.smith%40corporation.com",
                    "timeZone": "Europe/Moscow"
                },
                "content": "https://[jira_server]/secure/attachment/701925/Hosts.xlsx",
                "created": "2017-01-09T11:44:26.000+0300",
                "filename": "Hosts.xlsx",
                "id": "701925",
                "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                "self": "https://[jira_server]/rest/api/2/attachment/701925",
                "size": 14381
            },
...
        ],
        "comment": {
            "comments": [
                {
                    "author": {
                        "active": true,
                        "avatarUrls": {
                            "16x16": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=16",
                            "24x24": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=24",
                            "32x32": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=32",
                            "48x48": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=48"
                        },
                        "displayName": "John Smith",
                        "emailAddress": "j.smith@corporation.com",
                        "key": "j.smith@corporation.com",
                        "name": "j.smith@corporation.com",
                        "self": "https://[jira_server]/rest/api/2/user?username=j.smith%40corporation.com",
                        "timeZone": "Europe/Moscow"
                    },
                    "body": "A very interesting comment",
                    "created": "2017-01-10T12:39:46.000+0300",
                    "id": "701925",
                    "self": "https://[jira_server]/rest/api/2/issue/1833537/comment/4192745",
                    "updateAuthor": {
                        "active": true,
                        "avatarUrls": {
                            "16x16": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=16",
                            "24x24": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=24",
                            "32x32": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=32",
                            "48x48": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=48"
                        },
                        "displayName": "John Smith",
                        "emailAddress": "j.smith@corporation.com",
                        "key": "j.smith@corporation.com",
                        "name": "j.smith@corporation.com",
                        "self": "https://[jira_server]/rest/api/2/user?username=j.smith%40corporation.com",
                        "timeZone": "Europe/Moscow"
                    },
                    "updated": "2017-01-10T12:39:46.000+0300"
                }
            ],
            "maxResults": 1,
            "startAt": 0,
            "total": 1
        },
...
    "id": "1833537",
    "key": "SECSCAN-1244",
    "self": "https://[jira_server]/rest/api/2/issue/1833537"
}

Now you can download the attachments using url in “content”: https://[jira_server]/secure/attachment/701925/Hosts.xlsx

curl -u jira_login:jira_password -X GET -H "Content-Type: application/json" https://[jira_server]/secure/attachment/701925/Hosts.xlsx -o "Hosts.xlsx"

Processing the task

You convert xlsx to parsable csv using ssconvert tool (part of gnumeric):

ssconvert "Hosts.xlsx" "Hosts.csv"

If you really want to know how perform vulnerability scans of your hosts automatically, check the post “Nessus API for hosts scanning“. You can also check similar stuff for Qualys, Rapid7 Nexpose and FSecure.

Adding a new comment

Now suppose we have scan results in the file ScanResults.txt and we want to add text from this file as a comment to the issue. We need to created ScanResults.json with structure: '{ "body": "Our comment" }'
This bash code can create such file for you. It's very ugly, but it works:

rm ScanResults.json
echo -n "{ \"body\": \"" >> ScanResults.json
cat ScanResults.txt | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g'>> ScanResults.json
echo "\" }" >> ScanResults.json

curl -u jira_login:jira_password -X POST --data @ScanResults.json -H "Content-Type: application/json" "https://[jira_server]/rest/api/2/issue/SECSCAN-1244/comment"

If you request the SECSCAN-1244 issue once again, you will see something new in the “comment” section:

curl -u jira_login:jira_password -X GET -H "Content-Type: application/json" "https://[jira_server]/rest/api/2/issue/SECSCAN-1244"

...
        "comment": {
            "comments": [
                {
                "author": {
                    "active": true,
                    "avatarUrls": {
                        "16x16": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=16",
                        "24x24": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=24",
                        "32x32": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=32",
                        "48x48": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=48"
                    },
                    "displayName": "John Smith",
                    "emailAddress": "j.smith@corporation.com",
                    "key": "j.smith@corporation.com",
                    "name": "j.smith@corporation.com",
                    "self": "https://[jira_server]/rest/api/2/user?username=j.smith%40corporation.com",
                    "timeZone": "Europe/Moscow"
                },
                    "body": "Scan result comment",
                    "created": "2017-01-11T15:56:01.000+0300",
                    "id": "4201223",
                    "self": "https://[jira_server]/rest/api/2/issue/1826537/comment/4201023",
                    "updateAuthor": {
                        "active": true,
                        "avatarUrls": {
                            "16x16": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=16",
                            "24x24": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=24",
                            "32x32": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=32",
                            "48x48": "https://[file_server]/avatar/d2216716462b11cc50ef22d2e3e32536?d=mm&s=48"
                        },
                        "displayName": "John Smith",
                        "emailAddress": "j.smith@corporation.com",
                        "key": "j.smith@corporation.com",
                        "name": "j.smith@corporation.com",
                        "self": "https://[jira_server]/rest/api/2/user?username=j.smith%40corporation.com",
                        "timeZone": "Europe/Moscow"
                    },
                    "updated": "2017-01-12T17:22:22.000+0300"
                },
...

Ok, let’s say tha we need to delete this comment. Use this request for it:

curl -u jira_login:jira_password -X DELETE -H "X-Atlassian-Token: no-check" https://[jira_server]/rest/api/2/issue/1826537/comment/4201023

Closing the issue

Ok, now we have fully processed the issue and ready to close it. First of all, you need to get available statuses:

curl -u jira_login:jira_password -X GET -H "Content-Type: application/json" https://[jira_server]/rest/api/2/issue/SECSCAN-1244/transitions

Here is one of them:

        {
            "id": "102",
            "name": "Closed",
            "to": {
                "description": "",
                "iconUrl": "https://[file_server]/images/icons/statuses/closed.png",
                "id": "10004",
                "name": "Closed",
                "self": "https://[jira_server]/rest/api/2/status/10005",
                "statusCategory": {
                    "colorName": "green",
                    "id": 3,
                    "key": "done",
                    "name": "\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e",
                    "self": "https://[jira_server]/rest/api/2/statuscategory/3"
                }
            }
        }

So, we now the transition id for “Closed” status and we can send a request to close the issue:

curl -D- -u jira_login:jira_password -X POST --data "{ \"transition\": { \"id\":\"102\" } }" -H "Content-Type: application/json" https://[jira_server]/rest/api/2/issue/SECSCAN-1244/transitions

As you can see, everything is very easy and intuitive. In the next post about Jira I am going to write about automated creating and managing the Jira issues, for example, for IT vulnerability remediation tasks.

Upd. There is no example of creating an issue. You can see it here “Creating an issue using a project key and field names”.

9 thoughts on “Automated task processing with JIRA API

  1. Nikolay

    Great article and tips.

    I had hope to see Python code to copy and paste for myself :D.

    Also its not great idea to use username/password in Curl, somebody can use it in its own environment (or will commit his/her scripts to github :D)

    Maybe there are some others ways to auth to Jira (keys, api keys, so on). Will check later for myself

    Reply
  2. Pingback: Vulnerability Management for Network Perimeter | Alexander V. Leonov

  3. Pingback: Atlassian Jira, Python and automated labeling | Alexander V. Leonov

  4. Pingback: Confluence REST API for reading and updating wiki pages | Alexander V. Leonov

  5. Pingback: Managing JIRA Scrum Sprints using API | Alexander V. Leonov

  6. Steve Stonebraker

    Great job on this! Finally I can close a ticket via The API! I found a ton of other content that was wrong before this page.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.