Skip to content

Commit 8723deb

Browse files
Merge pull request #66 from iriusrisk/release/1.1.0
[release/1.1.0] to main
2 parents 0aa9f01 + bcf8d77 commit 8723deb

22 files changed

+1462
-235
lines changed

startleft/api/controllers/iac/file_type.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

startleft/api/controllers/iac/iac_create_otm_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
@router.post(URL, status_code=RESPONSE_STATUS_CODE, description="Generates an OTM threat model from an IaC file")
3636
def iac(iac_file: UploadFile = File(..., description="File that contains the Iac definition"),
37-
iac_type: IacType = Form(..., description="Type of IaC File: CLOUDFORMATION"),
37+
iac_type: IacType = Form(..., description="Type of IaC File: CLOUDFORMATION, TERRAFORM"),
3838
id: str = Form(..., description="ID of the new project"),
3939
name: str = Form(..., description="Name of the new project"),
4040
mapping_file: UploadFile = File(..., description="File that contains the mapping between IaC "
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
from enum import Enum
22

3+
from startleft.config import paths
4+
35

46
class IacType(str, Enum):
5-
JSON = 'CLOUDFORMATION'
7+
CLOUDFORMATION = ("CLOUDFORMATION", "CloudFormation", paths.default_cf_mapping_file)
8+
TERRAFORM = ("TERRAFORM", "Terraform", paths.default_tf_aws_mapping_file)
9+
10+
def __new__(cls, value, description: str = None, def_map_file=None):
11+
obj = str.__new__(cls, [value])
12+
obj._value_ = value
13+
obj.description = description
14+
obj.def_map_file = def_map_file
615

16+
return obj

startleft/cli.py

Lines changed: 69 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import logging
2+
import re
23
import sys
34

45
import click
56

7+
from startleft.api.controllers.iac.iac_type import IacType
68
from startleft.api.errors import CommonError
79
from startleft.iac_to_otm import IacToOtm
10+
from startleft.messages import messages
811
from startleft.project.iriusrisk_project_repository import IriusriskProjectRepository
912
from startleft.project.otm_project import OtmProject
1013
from startleft.project.otm_project_service import OtmProjectService
@@ -14,18 +17,18 @@
1417
__version__ = "0.1.0"
1518

1619

17-
def validate_logging(ctx, param, value):
20+
def get_log_level(ctx, param, value):
1821
levels = {
19-
'none': 100,
20-
'crit': logging.CRITICAL,
21-
'error': logging.ERROR,
22-
'warn': logging.WARNING,
23-
'info': logging.INFO,
24-
'debug': logging.DEBUG
22+
'NONE': 100,
23+
'CRIT': logging.CRITICAL,
24+
'ERROR': logging.ERROR,
25+
'WARN': logging.WARNING,
26+
'INFO': logging.INFO,
27+
'DEBUG': logging.DEBUG
2528
}
2629

27-
if value.lower() in levels:
28-
return levels[value.lower()]
30+
if value.upper() in levels:
31+
return levels[value.upper()]
2932
raise click.BadParameter(f"Log level must be one of: {', '.join(levels.keys())}")
3033

3134

@@ -37,6 +40,16 @@ def configure_logger(level, verbose):
3740
logging.basicConfig(format='%(message)s', level=level)
3841

3942

43+
def validate_server(ctx, param, value):
44+
regex = "((http|https)://)(www.)?[a-zA-Z0-9@:%._\\+~#?&//=]{2,256}(\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%._\\+~#?&//=]*))?(:[0-9]+)?"
45+
p = re.compile(regex)
46+
47+
if value is None or not re.search(p, value):
48+
raise click.BadParameter("IriusRisk host must follow the convention 'proto://server[:port]'")
49+
50+
return value
51+
52+
4053
class CatchAllExceptions(click.Group):
4154
def __call__(self, *args, **kwargs):
4255
try:
@@ -49,9 +62,10 @@ def __call__(self, *args, **kwargs):
4962

5063

5164
@click.group(cls=CatchAllExceptions)
52-
@click.option('--log-level', '-l', callback=validate_logging, default='info',
53-
help='Set the log level. Must be one of: crit, error, warn, info, debug, none.')
54-
@click.option('--verbose/--no-verbose', default=False, help='Makes logging more verbose.')
65+
@click.option('--log-level', '-l',
66+
type=click.Choice(['CRIT', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'NONE'], case_sensitive=False),
67+
callback=get_log_level, default='info', help='Set the log level.')
68+
@click.option('--verbose/--no-verbose', '-v/-nv', default=False, help='Makes logging more verbose.')
5569
@click.version_option(__version__)
5670
def cli(log_level, verbose):
5771
"""
@@ -61,100 +75,90 @@ def cli(log_level, verbose):
6175

6276

6377
@cli.command()
64-
@click.option('--type', '-t',
65-
type=click.Choice(['JSON', 'YAML', 'CloudFormation', 'HCL2', 'Terraform', 'XML'], case_sensitive=False),
66-
default="JSON",
67-
help='Specify the source file type.')
68-
@click.option('--map', '-m', help='Map file to use when parsing source files')
69-
@click.option('--otm', '-o', default='threatmodel.otm', help='OTM output file name')
70-
@click.option('--name', '-n', help='Project name')
71-
@click.option('--id', help='Project ID')
72-
@click.option('--recreate/--no-recreate', default=False, help='Delete and recreate the project each time')
73-
@click.option('--irius-server', default='', envvar='IRIUS_SERVER',
74-
help='IriusRisk server to connect to (proto://server[:port])')
75-
@click.option('--api-token', default='', envvar='IRIUS_API_TOKEN', help='IriusRisk API token')
76-
@click.argument('filename', nargs=-1)
77-
def run(type, map, otm, name, id, recreate, irius_server, api_token, filename):
78+
@click.option(messages.IAC_TYPE_NAME, messages.IAC_TYPE_SHORTNAME, type=click.Choice(messages.IAC_TYPE_SUPPORTED, case_sensitive=False), required=True, help=messages.IAC_TYPE_DESC)
79+
@click.option(messages.MAPPING_FILE_NAME, messages.MAPPING_FILE_SHORTNAME, help=messages.MAPPING_FILE_DESC)
80+
@click.option(messages.OUTPUT_FILE_NAME, messages.OUTPUT_FILE_SHORTNAME, default=messages.OUTPUT_FILE, help=messages.OUTPUT_FILE_DESC)
81+
@click.option(messages.PROJECT_NAME_NAME, messages.PROJECT_NAME_SHORTNAME, required=True, help=messages.PROJECT_NAME_DESC)
82+
@click.option(messages.PROJECT_ID_NAME, messages.PROJECT_ID_SHORTNAME, required=True, help=messages.PROJECT_ID_DESC)
83+
@click.option(messages.RECREATE_NAME, messages.RECREATE_SHORTNAME, default=False, help=messages.RECREATE_DESC)
84+
@click.option(messages.IRIUS_SERVER_NAME, messages.IRIUS_SERVER_SHORTNAME, default='', envvar=messages.IRIUS_SERVER_ENVAR, callback=validate_server, help=messages.IRIUS_SERVER_DESC)
85+
@click.option(messages.API_TOKEN_NAME, messages.API_TOKEN_SHORTNAME, default='', envvar=messages.API_TOKEN_ENVAR, help=messages.API_TOKEN_DESC)
86+
@click.argument(messages.IAC_FILE_NAME, required=True, nargs=-1)
87+
def run(iac_type, mapping_file, output_file, project_name, project_id, recreate, irius_server, api_token,
88+
iac_file):
7889
"""
79-
Parses IaC source files into Open Threat Model and immediately uploads threat model to IriusRisk
90+
Parses an IaC file into an Open Threat Model (OTM) and uploads it to IriusRisk
8091
"""
8192
logger.info("Parsing IaC source files into OTM")
82-
otm_project = OtmProject.from_iac_file(id, name, type, filename, map, otm)
93+
otm_project = OtmProject.from_iac_file(project_id, project_name, IacType(iac_type.upper()), iac_file,
94+
mapping_file, output_file)
8395
otm_service = OtmProjectService(IriusriskProjectRepository(api_token, irius_server))
8496

8597
__create_or_recreate_otm_project(recreate, otm_service, otm_project)
8698

8799

88100
@cli.command()
89-
@click.option('--type', '-t',
90-
type=click.Choice(['JSON', 'YAML', 'CloudFormation', 'HCL2', 'Terraform', 'XML'], case_sensitive=False),
91-
default="JSON",
92-
help='Specify the source file type.')
93-
@click.option('--map', '-m', help='Map file to use when parsing source files')
94-
@click.option('--otm', '-o', default='threatmodel.otm', help='OTM output file name')
95-
@click.option('--name', '-n', help='Project name')
96-
@click.option('--id', help='Project ID')
97-
@click.argument('filename', nargs=-1)
98-
def parse(type, map, otm, name, id, filename):
101+
@click.option(messages.IAC_TYPE_NAME, messages.IAC_TYPE_SHORTNAME, type=click.Choice(messages.IAC_TYPE_SUPPORTED, case_sensitive=False), required=True, help=messages.IAC_TYPE_DESC)
102+
@click.option(messages.MAPPING_FILE_NAME, messages.MAPPING_FILE_SHORTNAME, help=messages.MAPPING_FILE_DESC)
103+
@click.option(messages.OUTPUT_FILE_NAME, messages.OUTPUT_FILE_SHORTNAME, default=messages.OUTPUT_FILE, help=messages.OUTPUT_FILE_DESC)
104+
@click.option(messages.PROJECT_NAME_NAME, messages.PROJECT_NAME_SHORTNAME, required=True, help=messages.PROJECT_NAME_DESC)
105+
@click.option(messages.PROJECT_ID_NAME, messages.PROJECT_ID_SHORTNAME, required=True, help=messages.PROJECT_ID_DESC)
106+
@click.argument(messages.IAC_FILE_NAME, required=True, nargs=-1)
107+
def parse(iac_type, mapping_file, output_file, project_name, project_id, iac_file):
99108
"""
100109
Parses IaC source files into Open Threat Model
101110
"""
102111
logger.info("Parsing IaC source files into OTM")
103-
OtmProject.from_iac_file(id, name, type, filename, map, otm)
112+
OtmProject.from_iac_file(project_id, project_name, IacType(iac_type.upper()), iac_file, mapping_file, output_file)
104113

105114

106115
@cli.command()
107-
@click.option('--recreate/--no-recreate', default=False, help='Delete and recreate the project each time')
108-
@click.option('--irius-server', default='', envvar='IRIUS_SERVER',
109-
help='IriusRisk server to connect to (proto://server[:port])')
110-
@click.option('--api-token', default='', envvar='IRIUS_API_TOKEN', help='IriusRisk API token')
111-
@click.argument('filename', nargs=-1)
112-
def threatmodel(recreate, irius_server, api_token, filename):
116+
@click.option(messages.RECREATE_NAME, messages.RECREATE_SHORTNAME, default=False, help=messages.RECREATE_DESC)
117+
@click.option(messages.IRIUS_SERVER_NAME, messages.IRIUS_SERVER_SHORTNAME, default='', envvar=messages.IRIUS_SERVER_ENVAR, callback=validate_server, help=messages.IRIUS_SERVER_DESC)
118+
@click.option(messages.API_TOKEN_NAME, messages.API_TOKEN_SHORTNAME, default='', envvar=messages.API_TOKEN_ENVAR, help=messages.API_TOKEN_DESC)
119+
@click.argument(messages.IAC_FILE_NAME, required=True, nargs=-1)
120+
def threatmodel(recreate, irius_server, api_token, iac_file):
113121
"""
114122
Uploads an OTM file to IriusRisk
115123
"""
116-
otm_project = OtmProject.from_otm_file(filename)
124+
otm_project = OtmProject.from_otm_file(iac_file)
117125
otm_service = OtmProjectService(IriusriskProjectRepository(api_token, irius_server))
118126

119127
__create_or_recreate_otm_project(recreate, otm_service, otm_project)
120128

121129

122130
@cli.command()
123-
@click.option('--map', '-m', multiple=True, help='Map file to validate')
124-
@click.option('--otm', '-o', multiple=True, help='OTM file to validate')
125-
def validate(map, otm):
131+
@click.option(messages.MAPPING_FILE_NAME, messages.MAPPING_FILE_SHORTNAME, help=messages.MAPPING_FILE_DESC)
132+
@click.option(messages.OUTPUT_FILE_NAME, messages.OUTPUT_FILE_SHORTNAME, default=messages.OUTPUT_FILE, help=messages.OUTPUT_FILE_DESC)
133+
def validate(mapping_file, output_file):
126134
"""
127135
Validates a mapping or OTM file
128136
"""
129-
if map:
137+
if mapping_file:
130138
logger.info("Validating IaC mapping files")
131-
OtmProject.validate_iac_mappings_file(map)
139+
OtmProject.validate_iac_mappings_file(mapping_file)
132140

133-
if otm:
141+
if output_file:
134142
logger.info("Validating OTM file")
135-
OtmProject.load_and_validate_otm_file(otm)
143+
OtmProject.load_and_validate_otm_file(output_file)
136144

137145

138146
@cli.command()
139-
@click.option('--type', '-t',
140-
type=click.Choice(['JSON', 'YAML', 'CloudFormation', 'HCL2', 'Terraform', 'XML'], case_sensitive=False),
141-
default="JSON",
142-
help='Specify the source file type.')
143-
@click.option('--query', help='JMESPath query to run against source files')
144-
@click.argument('filename', nargs=-1)
145-
def search(type, query, filename):
147+
@click.option(messages.IAC_TYPE_NAME, messages.IAC_TYPE_SHORTNAME, type=click.Choice(messages.IAC_TYPE_SUPPORTED, case_sensitive=False), required=True, help=messages.IAC_TYPE_DESC)
148+
@click.option('--query', '-q', help='JMESPath query to run against the IaC file.')
149+
@click.argument(messages.IAC_FILE_NAME, required=True, nargs=-1)
150+
def search(iac_type, query, iac_file):
146151
"""
147152
Searches source files for the given query
148153
"""
149154

150-
iac_to_otm = IacToOtm(None, None)
151-
logger.info("Running JMESPath search query against source files")
152-
iac_to_otm.search(type, query, filename)
155+
iac_to_otm = IacToOtm(None, None, IacType(iac_type.upper()))
156+
logger.info("Running JMESPath search query against the IaC file")
157+
iac_to_otm.search(IacType(iac_type.upper()), query, iac_file)
153158

154159

155-
@click.option('--port', default=5000, envvar='application port',
156-
help='The port to deploy this application to')
157160
@cli.command()
161+
@click.option('--port', '-p', default=5000, envvar='STARTLEFT_PORT', help='Startleft deployment port.')
158162
def server(port: int):
159163
"""
160164
Launches the REST server to generate OTMs from requests

startleft/config/default-terraform-aws-mapping.yaml

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)