Skip to main content
Skip table of contents

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

  1. Create a new Python project using your preferred dependency management system. For example, you could create a python3 virtual environment:

CODE
$ virtualenv security-for-confluence-jira-integration-env
# ...
$ source security-for-confluence-jira-integration-env/bin/activate
  1. Install the following required dependencies: click, atlassian-python-api, and strictyaml (for Prometheus-flavored configuration).

CODE
$ pip install click atlassian-python-api strictyaml
  1. 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.

CODE
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
  1. Create the following integration script:

security-for-confluence-jira-integration.py

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()
  1. 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.

  2. 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.

  3. 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.

    1. 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.

  4. 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.

  1. 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.

CODE
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.
  1. 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

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.