Security for Confluence Data Center Cookbook
The following recipes are provided for informational purposes only and any code here is provided on an “as-is” basis. The reader assumes all risk of any use of the code in this Cookbook. THERE IS NO WARRANTY ON THIS SOFTWARE, EXPRESS OR IMPLIED, AND ALL WARRANTIES ARE EXPRESSLY DISCLAIMED HEREIN, INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
Integrate Security for Confluence Data Center with Jira
Introduction
While Security for Confluence provides a rich interface for processing your findings, it may be the case that utilizing a Jira project with tickets for each finding fits with your organizational goals in terms of metrics and disposition frequency. For example, Jira tickets allow comments where the person who processes the finding can track the work required to rotate a secret, or track the nuance in a particularly odd false-positive, or provide a custom outcome when a finding is marked “Reviewed”. Jira tickets can also be linked to other related projects easily, which could allow them to be delegated to teams responsible for the affected content itself, for example.
The following solution uses the Soteri Security for Confluence Data Center REST API and the Jira Data Center REST API to pull findings from Soteri API and create issues using the Jira API. While Jira Cloud is not covered here, it is certainly possible to adapt this script to create Jira Cloud tickets.
In this recipe you will learn:
How to use the Soteri Security for Confluence Data Center REST API to gather information about findings needed to make actionable tasks in a service management product
How to generate Jira tickets using the Jira Data Center REST API from Soteri Security for Confluence findings
How to ensure your findings and your tickets are synchronized and not duplicated.
Steps
Create a new Python project using your preferred dependency management system. For example, you could create a python3 virtual environment:
$ virtualenv security-for-confluence-jira-integration-env
# ...
$ source security-for-confluence-jira-integration-env/bin/activate
Install the following required dependencies:
click
,atlassian-python-api
, andstrictyaml
(for Prometheus-flavored configuration).
$ pip install click atlassian-python-api strictyaml
Create the following configuration file in
/etc/soteri/security-for-confluence-jira-integration.yaml
.
Relying on OS permissions to protect credentials on disk may not be approved by your organizational security policy - in which case, you will need to find an alternative way to provide your Confluence and Jira credentials.
confluence:
url: http://your-instance-here/confluence
username: your-username
password: your-password
jira:
url: http://your-instance-here/jira
username: your-username
password: your-password
custom_field_id: 10201
project_key: INFOSEC
issue_type_id: 10100
Create the following integration script:
security-for-confluence-jira-integration.py
#!/usr/bin/env python
import sys
from hashlib import sha512
import click
from atlassian import Confluence, Jira
from strictyaml import load
def get_findings(confluence):
for space in confluence.get_all_spaces()['results']:
base_url = f'/rest/security/latest/scan/space/{space['key']}'
page = 0
size = 100
total: int|None = None
while total is None or page*size < total:
resp = confluence.get(f"{base_url}?page={page}&size={size}")
total = resp['findings']['totalCount']
for finding in resp['findings']['values']:
yield finding | {"spaceKey": space['key']}
page += 1
def format_jira_payload(finding, issue_hash_field, project_key, issue_type_id, url):
space_key = finding['spaceKey']
content_id = finding['contentId']
version = finding['versionNumber']
lines = f"{finding['path']}:{finding['lineOffsetStart']}-{finding['lineOffsetEnd']}"
report_link = (f"{url}/plugins/servlet/soteri/security-scan-report?"
f"spaceKey={space_key}&id={content_id}&versionNumber={version}")
text_hash = sha512((finding['text']+lines).encode()).hexdigest()
return {
"project": {"key": project_key},
"summary": f"{finding['ruleName']} finding in {space_key} - {finding['pageName']}:{lines}",
"description": f"""
h3. A potential secret leak was detected by Soteri Security for Confluence:
Title: {finding['pageName']}
Rule: {finding['ruleName']}
Lines: {lines}
Link: {finding['url']}
[Report Link|{report_link}]
""",
"issuetype": {"id": issue_type_id},
f"customfield_{issue_hash_field}": text_hash
}
def init_config(filename):
with open(filename, 'w') as file:
file.write("""confluence:
url: http://your-instance-here/confluence
username: your-username
password: your-password
jira:
url: http://your-instance-here/jira
username: your-username
password: your-password
custom_field_id: NNNNN
project_key: INFOSEC
issue_type_id: 10100""")
@click.command()
@click.option("--config-filename", default='/etc/soteri/security-for-confluence-jira-integration.yaml',
help="YAML configuration filename")
@click.option("--dry-run", is_flag=True, help="Don't make any changes")
@click.option("--init", is_flag=True, help="Generate a YAML config")
def cli(config_filename, dry_run, init):
if init:
init_config(config_filename)
click.echo(f"Initialized config file at {config_filename}")
sys.exit(0)
with open(config_filename) as config_file:
config = load(config_file.read())
confluence = Confluence(**config['confluence'].data)
jira = Jira(**config['jira'].data)
issue_hash_field = config['custom_field_id'].data
project_key = config['project_key'].data
issue_type_id = config['issue_type_id'].data
confluence_url = config['confluence']['url'].data
issues = [format_jira_payload(finding, issue_hash_field, project_key, issue_type_id, confluence_url)
for finding in get_findings(confluence)]
for issue in issues:
search_string = (f"project = {project_key} AND "
f"cf[{issue_hash_field}] ~ '{issue["customfield_"+issue_hash_field]}'")
if jira.jql(search_string)['total'] == 0:
if dry_run:
click.echo(f"Would have created issue {issue}")
else:
click.echo(f"Created {issue["summary"]}")
jira.create_issue(issue)
else:
click.echo(f"Skipping already-created issue with summary '{issue['summary']}'")
click.echo("Finished.")
if __name__ == '__main__':
cli()
Create a custom field in your Jira instance which will hold the cryptographic hash of the finding and the lines. This ensures that we can associate issues to the unique findings even if they’ve been dispositioned. It is recommended to make this a single-line text field type.
Get the custom field id for the custom field you just made. Save this under
custom_field_id
in the YAML configuration file you made above.Decide which project you would like to create Jira issues in. Save that project’s key under
project_key
in the YAML configuration file you made above.Ensure that your custom field is visible in the creation interface. You can accomplish this by going to Jira Administration → Issues → Screens → “Issue Creation Screen” for the name of your project. In the “Configure Screen” view, add your new custom field.
Find the issue type you would like to create. You can create a new issue type, or you can find the issue type ID under Jira Administration → Issues → Issue Types → Edit (next to your issue type). The Issue type ID will be in your browser’s location bar:
/jira/secure/admin/EditIssueType!default.jspa?id=10100
In this case, you would save issue_type_id: 10100
in the YAML configuration file you made above.
With Jira configured and your configuration file completed, it’s time to test:
security-for-confluence-jira-integration.py --dry-run
Which will tell you which issues it would have created.
If everything is good, run without the --dry-run
flag.
Created CREDIT_CARD_NUMBERS finding in DEV - expense-report.pdf:/body/div[2]/p[2]:18-37
Created FACEBOOK_SECRET_KEY finding in DEV - Developer Guide for Facebook Integrations:/body/div/p[1]:22-62
Created FACEBOOK_SECRET_KEY finding in DEV - Developer Guide for Facebook Integrations:/body/div/p[2]:11-57
Created FACEBOOK_SECRET_KEY finding in DEV - Developer Guide for Facebook Integrations:/body/div/p[3]:8-55
Created GOOGLE_API_KEY finding in DEV - Developer Guide for Google APIs:/body/div/p:15-54
Finished.

Now, you could schedule the script as a cron job. You can accomplish this by adding something like the following line to your system’s crontab:
0 0 * * * /home/integrator/security-for-confluence-jira-integration-env/bin/python /home/integrator/security-for-confluence-jira-integration.py