updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,210 @@
|
||||
import logging
|
||||
import sys
|
||||
from enum import Enum
|
||||
|
||||
import typer
|
||||
from rich.prompt import Prompt
|
||||
|
||||
from safety.console import main_console as console
|
||||
from safety.decorators import notify
|
||||
from safety.events.utils import emit_firewall_disabled
|
||||
from typing import List, Optional
|
||||
|
||||
# TODO: refactor this import and the related code
|
||||
# For now, let's keep it as is
|
||||
from safety.error_handlers import handle_cmd_exception
|
||||
|
||||
from ..cli_util import (
|
||||
CommandType,
|
||||
FeatureType,
|
||||
SafetyCLICommand,
|
||||
SafetyCLISubGroup,
|
||||
pass_safety_cli_obj,
|
||||
)
|
||||
from ..constants import (
|
||||
CONTEXT_COMMAND_TYPE,
|
||||
CONTEXT_FEATURE_TYPE,
|
||||
EXIT_CODE_OK,
|
||||
DEFAULT_EPILOG,
|
||||
)
|
||||
from ..tool.interceptors import create_interceptor
|
||||
from ..tool.main import reset_system
|
||||
from .constants import (
|
||||
FIREWALL_CMD_NAME,
|
||||
FIREWALL_HELP,
|
||||
MSG_FEEDBACK,
|
||||
MSG_REQ_FILE_LINE,
|
||||
MSG_UNINSTALL_EXPLANATION,
|
||||
MSG_UNINSTALL_WRAPPERS,
|
||||
MSG_UNINSTALL_CONFIG,
|
||||
MSG_UNINSTALL_SUCCESS,
|
||||
UNINSTALL_CMD_NAME,
|
||||
UNINSTALL_HELP,
|
||||
INIT_CMD_NAME,
|
||||
INIT_HELP,
|
||||
MSG_INIT_SUCCESS,
|
||||
)
|
||||
|
||||
|
||||
firewall_app = typer.Typer(
|
||||
rich_markup_mode="rich", cls=SafetyCLISubGroup, name=FIREWALL_CMD_NAME
|
||||
)
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
init_app = typer.Typer(rich_markup_mode="rich", cls=SafetyCLISubGroup)
|
||||
|
||||
|
||||
@firewall_app.callback(
|
||||
cls=SafetyCLISubGroup,
|
||||
help=FIREWALL_HELP,
|
||||
epilog=DEFAULT_EPILOG,
|
||||
context_settings={
|
||||
"allow_extra_args": True,
|
||||
"ignore_unknown_options": True,
|
||||
CONTEXT_COMMAND_TYPE: CommandType.BETA,
|
||||
CONTEXT_FEATURE_TYPE: FeatureType.FIREWALL,
|
||||
},
|
||||
)
|
||||
@pass_safety_cli_obj
|
||||
def firewall(ctx: typer.Context) -> None:
|
||||
"""
|
||||
Main callback for the firewall commands.
|
||||
|
||||
Args:
|
||||
ctx (typer.Context): The Typer context object.
|
||||
"""
|
||||
LOG.info("firewall callback started")
|
||||
|
||||
|
||||
@firewall_app.command(
|
||||
cls=SafetyCLICommand,
|
||||
name=UNINSTALL_CMD_NAME,
|
||||
help=UNINSTALL_HELP,
|
||||
options_metavar="[OPTIONS]",
|
||||
context_settings={
|
||||
"allow_extra_args": True,
|
||||
"ignore_unknown_options": True,
|
||||
CONTEXT_COMMAND_TYPE: CommandType.BETA,
|
||||
CONTEXT_FEATURE_TYPE: FeatureType.FIREWALL,
|
||||
},
|
||||
)
|
||||
@handle_cmd_exception
|
||||
@notify
|
||||
def uninstall(ctx: typer.Context):
|
||||
console.print()
|
||||
console.print(MSG_UNINSTALL_EXPLANATION)
|
||||
|
||||
console.print()
|
||||
prompt = "Uninstall?"
|
||||
should_uninstall = (
|
||||
Prompt.ask(
|
||||
prompt=prompt,
|
||||
choices=["y", "n"],
|
||||
default="y",
|
||||
show_default=True,
|
||||
console=console,
|
||||
).lower()
|
||||
== "y"
|
||||
)
|
||||
|
||||
if not should_uninstall:
|
||||
sys.exit(EXIT_CODE_OK)
|
||||
|
||||
console.print()
|
||||
for msg in MSG_UNINSTALL_CONFIG:
|
||||
console.print(msg)
|
||||
# TODO: Make it robust. The reset per tool should be included in remove
|
||||
# interceptors
|
||||
reset_system()
|
||||
|
||||
# TODO: support reset project files
|
||||
|
||||
console.print(MSG_UNINSTALL_WRAPPERS)
|
||||
interceptor = create_interceptor()
|
||||
interceptor.remove_interceptors()
|
||||
|
||||
console.print()
|
||||
console.print(MSG_UNINSTALL_SUCCESS)
|
||||
|
||||
console.print()
|
||||
console.print(MSG_REQ_FILE_LINE)
|
||||
|
||||
console.print()
|
||||
|
||||
console.print(MSG_FEEDBACK)
|
||||
|
||||
console.print()
|
||||
prompt = "Feedback (or enter to exit)"
|
||||
feedback = Prompt.ask(prompt)
|
||||
feedback = None if len(feedback) <= 0 else feedback
|
||||
|
||||
emit_firewall_disabled(event_bus=ctx.obj.event_bus, reason=feedback)
|
||||
|
||||
if feedback:
|
||||
console.print()
|
||||
console.print("Thank you for your feedback!")
|
||||
|
||||
|
||||
class ToolChoice(str, Enum):
|
||||
pip = "pip"
|
||||
poetry = "poetry"
|
||||
uv = "uv"
|
||||
npm = "npm"
|
||||
|
||||
|
||||
@firewall_app.command(
|
||||
cls=SafetyCLICommand,
|
||||
name=INIT_CMD_NAME,
|
||||
help=INIT_HELP,
|
||||
options_metavar="[OPTIONS]",
|
||||
context_settings={
|
||||
"allow_extra_args": True,
|
||||
"ignore_unknown_options": True,
|
||||
CONTEXT_COMMAND_TYPE: CommandType.BETA,
|
||||
CONTEXT_FEATURE_TYPE: FeatureType.FIREWALL,
|
||||
},
|
||||
)
|
||||
@handle_cmd_exception
|
||||
@notify
|
||||
def init(
|
||||
ctx: typer.Context,
|
||||
tool: Optional[List[ToolChoice]] = typer.Option(
|
||||
None,
|
||||
"--tool",
|
||||
help="Specify one or more tools to initialize. If not specified, all tools will be used.",
|
||||
),
|
||||
):
|
||||
console.print()
|
||||
|
||||
interceptor = create_interceptor()
|
||||
|
||||
# If no tools specified, use all tools
|
||||
if not tool:
|
||||
selected_tools = list(interceptor.tools.keys())
|
||||
console.print("No tools specified. Using all available tools.")
|
||||
console.line()
|
||||
else:
|
||||
selected_tools = [t.value for t in tool]
|
||||
|
||||
console.print(
|
||||
f"Initializing safety firewall for tools: {', '.join(selected_tools)}"
|
||||
)
|
||||
|
||||
interceptor.install_interceptors(tools=selected_tools)
|
||||
console.print()
|
||||
|
||||
console.print(MSG_INIT_SUCCESS.format(", ".join(selected_tools)))
|
||||
|
||||
MSG_COMMAND_TO_RUN = "`source ~/.safety/.safety_profile`"
|
||||
|
||||
MSG_SETUP_NEXT_STEPS_MANUAL_STEP = (
|
||||
"(Don't forget to restart the terminal now!)"
|
||||
if sys.platform == "win32"
|
||||
else f"(Don't forget to run {MSG_COMMAND_TO_RUN} now!)"
|
||||
)
|
||||
|
||||
console.print()
|
||||
console.print(MSG_SETUP_NEXT_STEPS_MANUAL_STEP)
|
||||
@@ -0,0 +1,25 @@
|
||||
MSG_UNINSTALL_EXPLANATION = "Would you like to uninstall Safety Firewall on this machine? Doing so will mean you are no longer protected from malicious or vulnerable packages."
|
||||
MSG_UNINSTALL_SUCCESS = "Safety Firewall has been uninstalled from your machine. Note that your individual requirements files may still reference Safety Firewall. You can remove these references by removing the following line from your requirements files:"
|
||||
MSG_REQ_FILE_LINE = "-i https://pkgs.safetycli.com/repository/public/pypi/simple/"
|
||||
|
||||
MSG_FEEDBACK = "We're sorry to see you go. If you have any feedback on how we can do better, we'd love to hear it. Otherwise hit enter to exit."
|
||||
|
||||
|
||||
UNINSTALL_HELP = "Uninstall Safety Firewall from your machine."
|
||||
|
||||
|
||||
FIREWALL_CMD_NAME = "firewall"
|
||||
UNINSTALL_CMD_NAME = "uninstall"
|
||||
|
||||
|
||||
FIREWALL_HELP = "[BETA] Manage Safety Firewall settings."
|
||||
|
||||
MSG_UNINSTALL_CONFIG = (
|
||||
"Removing global configuration for pip from: ~/.config/pip/pip.conf",
|
||||
"Removing global configuration for uv from: uv.toml",
|
||||
)
|
||||
MSG_UNINSTALL_WRAPPERS = "Removing aliases to safety from config files"
|
||||
|
||||
INIT_CMD_NAME = "init"
|
||||
INIT_HELP = "Initialize Safety Firewall on this machine."
|
||||
MSG_INIT_SUCCESS = "Safety Firewall has been initialized on your machine. The following tools are now protected: {}"
|
||||
@@ -0,0 +1,4 @@
|
||||
from .utils import register_event_handlers
|
||||
|
||||
|
||||
__all__ = ["register_event_handlers"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from safety.events.handlers import EventHandler
|
||||
from safety.events.types import EventBusReadyEvent
|
||||
from safety.events.utils import emit_firewall_heartbeat
|
||||
|
||||
from safety.tool import ToolInspector
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from safety.events.event_bus import EventBus
|
||||
|
||||
|
||||
class HeartbeatInspectionEventHandler(EventHandler[EventBusReadyEvent]):
|
||||
"""
|
||||
Inspect the system for installed tools and send an emit
|
||||
a firewall heartbeat event.
|
||||
"""
|
||||
|
||||
def __init__(self, event_bus: "EventBus") -> None:
|
||||
super().__init__()
|
||||
self.event_bus = event_bus
|
||||
|
||||
async def handle(self, event: EventBusReadyEvent):
|
||||
ctx = event.payload.ctx
|
||||
inspector = ToolInspector(timeout=1.0)
|
||||
tools = await inspector.inspect_all_tools()
|
||||
|
||||
emit_firewall_heartbeat(self.event_bus, ctx, tools=tools)
|
||||
@@ -0,0 +1,40 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from safety_schemas.models.events import EventType
|
||||
from safety.events.event_bus import EventBus
|
||||
from safety.events.types import InternalEventType
|
||||
|
||||
from .handlers import HeartbeatInspectionEventHandler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from safety.models import SafetyCLI
|
||||
|
||||
|
||||
def register_event_handlers(event_bus: "EventBus", obj: "SafetyCLI") -> None:
|
||||
"""
|
||||
Subscribes to the firewall events that are relevant to the current context.
|
||||
"""
|
||||
handle_inspection = HeartbeatInspectionEventHandler(event_bus=event_bus)
|
||||
event_bus.subscribe([InternalEventType.EVENT_BUS_READY], handle_inspection)
|
||||
|
||||
if sec_events_handler := obj.security_events_handler:
|
||||
event_bus.subscribe(
|
||||
[
|
||||
EventType.FIREWALL_CONFIGURED,
|
||||
EventType.FIREWALL_HEARTBEAT,
|
||||
EventType.FIREWALL_DISABLED,
|
||||
EventType.PACKAGE_INSTALLED,
|
||||
EventType.PACKAGE_UNINSTALLED,
|
||||
EventType.PACKAGE_UPDATED,
|
||||
EventType.TOOL_COMMAND_EXECUTED,
|
||||
EventType.INIT_STARTED,
|
||||
EventType.FIREWALL_SETUP_RESPONSE_CREATED,
|
||||
EventType.FIREWALL_SETUP_COMPLETED,
|
||||
EventType.CODEBASE_DETECTION_STATUS,
|
||||
EventType.CODEBASE_SETUP_RESPONSE_CREATED,
|
||||
EventType.CODEBASE_SETUP_COMPLETED,
|
||||
EventType.INIT_SCAN_COMPLETED,
|
||||
EventType.INIT_EXITED,
|
||||
],
|
||||
sec_events_handler,
|
||||
)
|
||||
Reference in New Issue
Block a user