Skip to content

Commit

Permalink
Merge pull request #6 from spark1security/ms/asana
Browse files Browse the repository at this point in the history
Added support to Asana
  • Loading branch information
blupants authored Nov 22, 2023
2 parents f6ac286 + 7995a18 commit 9046e15
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/n0s1.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@


# n0s1 - Secret Scanner
n0s1 ([pronunciation](https://en.wiktionary.org/wiki/nosy#Pronunciation)) is a secret scanner for Jira, Confluence and Linear.app. It scans all tickets/items/issues within the chosen platform in search of any leaked secrets in the titles, bodies, and comments. It is open-source and it can be easily extended to support scanning many others Project Management and Issue Tracker platforms.
n0s1 ([pronunciation](https://en.wiktionary.org/wiki/nosy#Pronunciation)) is a secret scanner for Jira, Confluence, Asana and Linear.app. It scans all tickets/items/issues within the chosen platform in search of any leaked secrets in the titles, bodies, and comments. It is open-source and it can be easily extended to support scanning many others Project Management and Issue Tracker platforms.

These secrets are identified by comparing them against an adaptable configuration file named [regex.toml](https://github.com/spark1security/n0s1/blob/main/src/n0s1/config/regex.toml). The scanner specifically looks for sensitive information, which includes:
These secrets are identified by comparing them against an adaptable configuration file named [regex.yaml](https://github.com/spark1security/n0s1/blob/main/src/n0s1/config/regex.yaml). Alternative TOML format is also supported: [regex.toml](https://github.com/spark1security/n0s1/blob/main/src/n0s1/config/regex.toml). The scanner specifically looks for sensitive information, which includes:
* Github Personal Access Tokens
* GitLab Personal Access Tokens
* AWS Access Tokens
Expand All @@ -25,6 +25,7 @@ These secrets are identified by comparing them against an adaptable configuratio
### Currently supported target platforms:
* [Jira](https://www.atlassian.com/software/jira)
* [Confluence](https://www.atlassian.com/software/confluence)
* [Asana](https://asana.com)
* [Linear](https://linear.app/)

### Quick Start
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ requests
toml
jira
pyyaml
atlassian-python-api
atlassian-python-api
asana
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def get_version():
setup(
name="n0s1",
version=get_version(),
description="Secret Scanner for Jira, Confluence and Linear. Prevent credential leaks with n0s1.",
description="Secret Scanner for Jira, Confluence, Asana and Linear. Prevent credential leaks with n0s1.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://spark1.us/n0s1",
Expand All @@ -47,7 +47,7 @@ def get_version():
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
], # Classifiers help users find your project by categorizing it https://pypi.org/classifiers/
keywords="security, cybersecurity, scanner, secret scanner, secret leak, data leak, Jira, Linear, security scanner",
keywords="security, cybersecurity, scanner, secret scanner, secret leak, data leak, Jira, Confluence, Asana, Linear, security scanner",
package_dir={"": "src"},
packages=find_packages(where="src"),
python_requires=">=3.8, <4",
Expand Down
2 changes: 1 addition & 1 deletion src/n0s1/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.13"
__version__ = "1.0.14"
5 changes: 1 addition & 4 deletions src/n0s1/config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
maintainer: "Spark 1"

general_params:
regex_file: "config/regex.toml"
regex_file: "config/regex.yaml"
report_file: "n0s1_report.json"
post_comment: false
skip_comment: false
Expand All @@ -15,6 +15,3 @@ comment_params:
contact_help: "contact@spark1.us"
label: "n0s1bot_auto_comment_e869dd5fa15ca0749a350aac758c7f56f56ad9be1"

jira_params:
server: "http://localhost:2990/jira"
email: ""
77 changes: 77 additions & 0 deletions src/n0s1/controllers/asana_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import logging


class AsanaControler():
def __init__(self):
self._client = None

def set_config(self, config):
import asana
TOKEN = config.get("token", "")
self._client = asana.Client.access_token(TOKEN)
return self.is_connected()

def get_name(self):
return "Asana"

def is_connected(self):
if self._client:
if user := self._client.users.get_user("me"):
logging.info(f"Logged to {self.get_name()} as {user}")
else:
logging.error(f"Unable to connect to {self.get_name()} instance. Check your credentials.")
return False

if spaces := self._client.workspaces.get_workspaces():
workspace_found = False
project_found = False
for s in spaces:
workspace_gid = s.get("gid", "")
if len(workspace_gid) > 0:
workspace_found = True
if projects := self._client.projects.get_projects_for_workspace(workspace_gid):
for p in projects:
project_found = True
break
if workspace_found:
if project_found:
return True
else:
logging.error(f"Unable to list {self.get_name()} projects. Check your permissions.")
else:
logging.error(f"Unable to list {self.get_name()} workspaces. Check your permissions.")
else:
logging.error(f"Unable to connect to {self.get_name()} instance. Check your credentials.")
return False

def get_data(self, include_coments=False):
if not self._client:
return None, None, None, None, None

if workspaces := self._client.workspaces.get_workspaces():
for w in workspaces:
workspace_gid = w.get("gid", "")
if projects := self._client.projects.get_projects_for_workspace(workspace_gid):
for p in projects:
project_gid = p.get("gid", "")
if tasks := self._client.tasks.get_tasks_for_project(project_gid, opt_fields=["name", "gid", "notes", "permalink_url"]):
for t in tasks:
comments = []
title = t.get("name", "")
task_gid = t.get("gid", "")
description = t.get("notes", "")
url = t.get("permalink_url", "")
if include_coments:
if stories := self._client.stories.get_stories_for_task(task_gid):
for s in stories:
if s.get("type", "").lower() == "comment".lower():
comment = s.get("text", "")
comments.append(comment)
yield title, description, comments, url, task_gid

def post_comment(self, task_gid, comment):
if not self._client:
return False
if comment_status := self._client.stories.create_story_for_task(task_gid, {"type": "comment", "text": comment}):
status = comment_status.get("text", "")
return len(status) > 0
1 change: 1 addition & 0 deletions src/n0s1/controllers/confluence_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def post_comment(self, issue, comment):
return False
comment = comment.replace("#", "0")
comment = html.escape(comment, quote=True)
status = -1
if comment_status := self._client.add_comment(issue, comment):
status = comment_status.get("id", "")
return int(status) > 0
4 changes: 4 additions & 0 deletions src/n0s1/controllers/platform_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ def get_platform(self, platform):
from . import jira_controller as jira_controller
from . import confluence_controller as confluence_controller
from . import linear_controller as linear_controller
from . import asana_controller as asana_controller
except Exception:
import n0s1.controllers.jira_controller as jira_controller
import n0s1.controllers.confluence_controller as confluence_controller
import n0s1.controllers.linear_controller as linear_controller
import n0s1.controllers.asana_controller as asana_controller

factory.register_platform("", jira_controller.JiraControler)
factory.register_platform("jira", jira_controller.JiraControler)
Expand All @@ -50,3 +52,5 @@ def get_platform(self, platform):
factory.register_platform("confluence_scan", confluence_controller.ConfluenceControler)
factory.register_platform("linear", linear_controller.LinearControler)
factory.register_platform("linear_scan", linear_controller.LinearControler)
factory.register_platform("asana", asana_controller.AsanaControler)
factory.register_platform("asana_scan", asana_controller.AsanaControler)
41 changes: 31 additions & 10 deletions src/n0s1/n0s1.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
except:
import n0s1.reporting.report_gitlab as report_gitlab

global report_json, report_file, cfg, DEBUG
global n0s1_version, report_json, report_file, cfg, DEBUG


def init_argparse() -> argparse.ArgumentParser:
global n0s1_version
"""Adds arguements that can be called from the command line
Returns:
Expand All @@ -37,10 +38,18 @@ def init_argparse() -> argparse.ArgumentParser:
install_path = os.path.dirname(os.path.abspath(__file__))
parser = argparse.ArgumentParser(
prog="n0s1",
description="""Secret scanner for Project Management platforms such as Jira, Confluence and Linear.
description="""Secret scanner for Project Management platforms such as Jira, Confluence, Asana and Linear.
""",
)

try:
here = pathlib.Path(__file__).parent.resolve()
init_file = pathlib.Path(here / "__init__.py")
n0s1_version = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', init_file.read_text(), re.M).group(1)
except Exception:
n0s1_version = "0.0.1"
parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + n0s1_version)

# Create parent subparser. Note `add_help=False` and creation via `argparse.`
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
Expand Down Expand Up @@ -127,8 +136,19 @@ def init_argparse() -> argparse.ArgumentParser:
help="Subcommands", dest="command", metavar="COMMAND"
)

asana_scan_parser = subparsers.add_parser(
"asana_scan", help="Scan Asana tasks", parents=[parent_parser]
)
asana_scan_parser.add_argument(
"--api-key",
dest="api_key",
nargs="?",
type=str,
help="Asana API key. Ref: https://developers.asana.com/docs/personal-access-token#generating-a-pat"
)

linear_scan_parser = subparsers.add_parser(
"linear_scan", help="Scan Linear tickets", parents=[parent_parser]
"linear_scan", help="Scan Linear issues", parents=[parent_parser]
)
linear_scan_parser.add_argument(
"--api-key",
Expand Down Expand Up @@ -373,7 +393,7 @@ def scan(regex_config, controller, scan_arguments):


def main():
global report_json, report_file, cfg, DEBUG
global n0s1_version, report_json, report_file, cfg, DEBUG

logging.basicConfig(level=logging.INFO)
parser = init_argparse()
Expand Down Expand Up @@ -404,12 +424,7 @@ def main():

datetime_now_obj = datetime.now(timezone.utc)
date_utc = datetime_now_obj.strftime("%Y-%m-%dT%H:%M:%S")
try:
here = pathlib.Path(__file__).parent.resolve()
init_file = pathlib.Path(here / "__init__.py")
n0s1_version = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]', init_file.read_text(), re.M).group(1)
except Exception:
n0s1_version = "0.0.1"

report_json = {"tool": {"name": "n0s1", "version": n0s1_version, "author": "Spark 1 Security"},
"scan_date": {"timestamp": datetime_now_obj.timestamp(), "date_utc": date_utc},
"regex_config": regex_config, "findings": {}}
Expand All @@ -428,6 +443,12 @@ def main():
TOKEN = args.api_key
controler_config = {"token": TOKEN}

elif command == "asana_scan":
TOKEN = os.getenv("ASANA_TOKEN")
if args.api_key and len(args.api_key) > 0:
TOKEN = args.api_key
controler_config = {"token": TOKEN}

elif command == "jira_scan":
SERVER = os.getenv("JIRA_SERVER")
EMAIL = os.getenv("JIRA_EMAIL")
Expand Down
2 changes: 1 addition & 1 deletion src/n0s1/reporting/report_gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def add_vulns(self, n0s1_data: dict):
{
"type": "regex",
"name": identifiers_name,
"url": "https://github.com/spark1security/n0s1/blob/main/src/n0s1/config/regex.toml",
"url": "https://github.com/spark1security/n0s1/blob/main/src/n0s1/config/regex.yaml",
"value": identifiers_value
}
],
Expand Down

0 comments on commit 9046e15

Please sign in to comment.