Skip to content

Commit

Permalink
feat: added safety firewall
Browse files Browse the repository at this point in the history
  • Loading branch information
jakub-safetycli authored and yeisonvargasf committed Feb 12, 2025
1 parent f97fb15 commit 581e110
Show file tree
Hide file tree
Showing 64 changed files with 4,149 additions and 723 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind",
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/developer/.ssh,type=bind,consistency=cached"
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/developer/.ssh,type=bind,consistency=cached",
"source=${localEnv:HOME}/.safety,target=/home/developer/.safety,type=bind,consistency=cached"
],

"remoteEnv": {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
#### Quick Test with Python Package
\`\`\`bash
# Download and run with uv
gh run download ${context.runId} -n dist
gh run download ${context.runId} -n dist -R pyupio/safety
uv run --with safety-${version}-py3-none-any.whl safety --version
\`\`\`
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
exit 1
fi
BRANCH_NAME="${{ inputs.branch-name }}"
SLUG=$(echo "$BRANCH_NAME" | iconv -t ascii//TRANSLIT | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)
SLUG=$(echo "$BRANCH_NAME" | iconv -t ascii//TRANSLIT | sed -r 's/[^a-zA-Z0-9]+/./g' | sed -r 's/^.+\|.+$//g' | tr A-Z a-z)
echo "SLUG=$SLUG" >> $GITHUB_OUTPUT
- name: Version bump (PR)
Expand Down
21 changes: 18 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
// This uses the default environment which is a virtual environment
// created by Hatch
"python": "${workspaceFolder}/.hatch/bin/python",
"console": "integratedTerminal"
"console": "integratedTerminal",
"justMyCode": false,
}
],
"inputs": [
Expand Down Expand Up @@ -64,23 +65,37 @@
"auth login",
"auth login --headless",
"auth logout",
"auth status",

// Scan commands
"scan",
"--key ADD-YOUR-API-KEY scan",
"--key $SAFETY_API_KEY scan",
"--stage cicd --key $SAFETY_API_KEY scan",
"scan --use-server-matching",
"scan --detailed-output",
"--debug scan",
"--disable-optional-telemetry scan",
"scan --output json --output-file json",
"scan --help",

// Firewall commands
"init --help",
"init local_prj", // Directory has to be created manually
"init",
"pip list",
"pip install insecure-package",
"pip install fastapi",

// Check commands
"check",
"--debug check",

// Other commands
"license",
"--help"
"--help",
"validate --help",
"--key foo --help",
"configure"
],
"default": "scan"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"cells": [],
"cells": [
{
"metadata": {},
"cell_type": "raw",
"source": "",
"id": "e4a30302820cf149"
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies = [
"setuptools>=65.5.1",
"typer>=0.12.1",
"typing-extensions>=4.7.1",
"python-levenshtein>=0.25.1",
]
license = "MIT"
license-files = ["LICENSES/*"]
Expand Down
9 changes: 8 additions & 1 deletion safety/alerts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from dataclasses import dataclass

from safety.constants import CONTEXT_COMMAND_TYPE

from . import github
from safety.util import SafetyPolicyFile
from safety.scan.constants import CLI_ALERT_COMMAND_HELP
Expand All @@ -17,6 +19,10 @@ def get_safety_cli_legacy_group():
from safety.cli_util import SafetyCLILegacyGroup
return SafetyCLILegacyGroup

def get_context_settings():
from safety.cli_util import CommandType
return {CONTEXT_COMMAND_TYPE: CommandType.UTILITY}

@dataclass
class Alert:
"""
Expand All @@ -33,7 +39,8 @@ class Alert:
policy: Any = None
requirements_files: Any = None

@click.group(cls=get_safety_cli_legacy_group(), help=CLI_ALERT_COMMAND_HELP, deprecated=True, utility_command=True)
@click.group(cls=get_safety_cli_legacy_group(), help=CLI_ALERT_COMMAND_HELP,
deprecated=True, context_settings=get_context_settings())
@click.option('--check-report', help='JSON output of Safety Check to work with.', type=click.File('r'), default=sys.stdin, required=True)
@click.option("--key", envvar="SAFETY_API_KEY",
help="API Key for safetycli.com's vulnerability database. Can be set as SAFETY_API_KEY "
Expand Down
50 changes: 35 additions & 15 deletions safety/auth/cli.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from datetime import datetime
import logging
import sys
from safety.auth.models import Auth
from datetime import datetime

from safety.auth.utils import is_email_verified
from safety.auth.models import Auth
from safety.auth.utils import initialize, is_email_verified
from safety.console import main_console as console
from safety.constants import MSG_FINISH_REGISTRATION_TPL, MSG_VERIFICATION_HINT
from safety.meta import get_version

try:
from typing import Annotated
Expand All @@ -15,22 +16,38 @@
from typing import Optional

import click
from typer import Typer
import typer
from rich.padding import Padding
from typer import Typer

from safety.auth.main import get_auth_info, get_authorization_data, get_token, clean_session
from safety.auth.main import (
clean_session,
get_auth_info,
get_authorization_data,
get_token,
)
from safety.auth.server import process_browser_callback
from ..cli_util import get_command_for, pass_safety_cli_obj, SafetyCLISubGroup

from .constants import MSG_FAIL_LOGIN_AUTHED, MSG_FAIL_REGISTER_AUTHED, MSG_LOGOUT_DONE, MSG_LOGOUT_FAILED, MSG_NON_AUTHENTICATED
from safety.scan.constants import CLI_AUTH_COMMAND_HELP, CLI_AUTH_HEADLESS_HELP, DEFAULT_EPILOG, CLI_AUTH_LOGIN_HELP, CLI_AUTH_LOGOUT_HELP, CLI_AUTH_STATUS_HELP


from rich.padding import Padding
from safety.scan.constants import (
CLI_AUTH_COMMAND_HELP,
CLI_AUTH_HEADLESS_HELP,
CLI_AUTH_LOGIN_HELP,
CLI_AUTH_LOGOUT_HELP,
CLI_AUTH_STATUS_HELP,
DEFAULT_EPILOG,
)

from ..cli_util import SafetyCLISubGroup, get_command_for, pass_safety_cli_obj
from .constants import (
MSG_FAIL_LOGIN_AUTHED,
MSG_FAIL_REGISTER_AUTHED,
MSG_LOGOUT_DONE,
MSG_LOGOUT_FAILED,
MSG_NON_AUTHENTICATED,
)

LOG = logging.getLogger(__name__)

auth_app = Typer(rich_markup_mode="rich")
auth_app = Typer(rich_markup_mode="rich", name="auth")



Expand Down Expand Up @@ -183,6 +200,8 @@ def login(

render_successful_login(ctx.obj.auth, organization=organization)

initialize(ctx, refresh=True)

console.print()
if ctx.obj.auth.org or ctx.obj.auth.email_verified:
console.print(
Expand Down Expand Up @@ -249,12 +268,13 @@ def status(ctx: typer.Context, ensure_auth: bool = False,
"""
LOG.info('status started')
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
from safety.util import get_safety_version
safety_version = get_safety_version()
safety_version = get_version()
console.print(f"[{current_time}]: Safety {safety_version}")

info = get_auth_info(ctx)

initialize(ctx, refresh=True)

if ensure_auth:
console.print("running: safety auth status --ensure-auth")
console.print()
Expand Down
142 changes: 54 additions & 88 deletions safety/auth/cli_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
from safety.auth.utils import S3PresignedAdapter, SafetyAuthSession, get_keys, is_email_verified
from safety.constants import REQUEST_TIMEOUT
from safety.scan.constants import CLI_KEY_HELP, CLI_PROXY_HOST_HELP, CLI_PROXY_PORT_HELP, CLI_PROXY_PROTOCOL_HELP, CLI_STAGE_HELP
from safety.scan.util import Stage
from safety.util import DependentOption, SafetyContext, get_proxy_dict

from functools import wraps

from safety.models import SafetyCLI
from safety_schemas.models import Stage

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -89,7 +87,7 @@ def load_auth_session(click_ctx: click.Context) -> None:
click_ctx (click.Context): The Click context object.
"""
if not click_ctx:
LOG.warn("Click context is needed to be able to load the Auth data.")
LOG.warning("Click context is needed to be able to load the Auth data.")
return

client = click_ctx.obj.auth.client
Expand Down Expand Up @@ -160,89 +158,57 @@ def decorator(func: Callable) -> Callable:
return decorator


def inject_session(func: Callable) -> Callable:
"""
Decorator that injects a session object into Click commands.
def inject_session(ctx: click.Context, proxy_protocol: Optional[str] = None,
proxy_host: Optional[str] = None,
proxy_port: Optional[str] = None,
key: Optional[str] = None,
stage: Optional[Stage] = None,
invoked_command: str = "") -> Any:

Builds the session object to be used in each command.
# Skip injection for specific commands that do not require authentication
if invoked_command in ["configure"]:
return

Args:
func (Callable): The Click command function.
org: Optional[Organization] = get_organization()

Returns:
Callable: The wrapped Click command function with session injection.
"""
@wraps(func)
def inner(ctx: click.Context, proxy_protocol: Optional[str] = None,
proxy_host: Optional[str] = None,
proxy_port: Optional[str] = None,
key: Optional[str] = None,
stage: Optional[Stage] = None, *args, **kwargs) -> Any:
"""
Inner function that performs the session injection.
Args:
ctx (click.Context): The Click context object.
proxy_protocol (Optional[str]): The proxy protocol.
proxy_host (Optional[str]): The proxy host.
proxy_port (Optional[int]): The proxy port.
key (Optional[str]): The API key.
stage (Optional[Stage]): The stage.
*args (Any): Additional arguments.
**kwargs (Any): Additional keyword arguments.
Returns:
Any: The result of the decorated function.
"""

if ctx.invoked_subcommand == "configure":
return

org: Optional[Organization] = get_organization()

if not stage:
host_stage = get_host_config(key_name="stage")
stage = host_stage if host_stage else Stage.development

proxy_config: Optional[Dict[str, str]] = get_proxy_dict(proxy_protocol,
proxy_host, proxy_port)

client_session, openid_config = build_client_session(api_key=key,
proxies=proxy_config)
keys = get_keys(client_session, openid_config)

auth = Auth(
stage=stage,
keys=keys,
org=org,
client_id=CLIENT_ID,
client=client_session,
code_verifier=generate_token(48)
)

if not ctx.obj:
from safety.models import SafetyCLI
ctx.obj = SafetyCLI()

ctx.obj.auth=auth

load_auth_session(ctx)

info = get_auth_info(ctx)

if info:
ctx.obj.auth.name = info.get("name")
ctx.obj.auth.email = info.get("email")
ctx.obj.auth.email_verified = is_email_verified(info)
SafetyContext().account = info["email"]
else:
SafetyContext().account = ""

@ctx.call_on_close
def clean_up_on_close():
LOG.debug('Closing requests session.')
ctx.obj.auth.client.close()

return func(ctx, *args, **kwargs)

return inner
if not stage:
host_stage = get_host_config(key_name="stage")
stage = host_stage if host_stage else Stage.development

proxy_config: Optional[Dict[str, str]] = get_proxy_dict(proxy_protocol,
proxy_host, proxy_port)

client_session, openid_config = build_client_session(api_key=key,
proxies=proxy_config)
keys = get_keys(client_session, openid_config)

auth = Auth(
stage=stage,
keys=keys,
org=org,
client_id=CLIENT_ID,
client=client_session,
code_verifier=generate_token(48)
)

if not ctx.obj:
ctx.obj = SafetyCLI()

ctx.obj.auth = auth

load_auth_session(ctx)

info = get_auth_info(ctx)

if info:
ctx.obj.auth.name = info.get("name")
ctx.obj.auth.email = info.get("email")
ctx.obj.auth.email_verified = is_email_verified(info)
SafetyContext().account = info["email"]
else:
SafetyContext().account = ""

@ctx.call_on_close
def clean_up_on_close():
LOG.debug('Closing requests session.')
ctx.obj.auth.client.close()
2 changes: 1 addition & 1 deletion safety/auth/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from safety.auth.models import Organization
from safety.auth.constants import CLI_AUTH_LOGOUT, CLI_CALLBACK, AUTH_CONFIG_USER, CLI_AUTH
from safety.constants import CONFIG
from safety.scan.util import Stage
from safety_schemas.models import Stage
from safety.util import get_proxy_dict


Expand Down
Loading

0 comments on commit 581e110

Please sign in to comment.