feat(connectors): add stubbed Bascule and PowerShell connectors
Bascule: session-based connector using AC as credential. Transport stubbed — Shellstream integration in future sprint. PowerShell: session-based connector using Kerberos credentials from CredentialResolver. PSRP transport stubbed — pypsrp integration in future sprint. Signed-off-by: Tyler King <tking@guildhouse.dev>
This commit is contained in:
parent
5a759f5e12
commit
eee8740ce8
2 changed files with 175 additions and 0 deletions
87
gsap_broker/connectors/bascule.py
Normal file
87
gsap_broker/connectors/bascule.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Copyright 2026 Guildhouse Dev
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Bascule connector — governed shell sessions via Shellstream.
|
||||
|
||||
The Bascule connector uses the AC as the credential. Bascule (the
|
||||
governed-shell runtime) validates ACs natively — no separate secrets
|
||||
backend is needed. The transport establishes a Shellstream session
|
||||
to the target endpoint via a Bascule proxy.
|
||||
|
||||
Real integration:
|
||||
The ``BasculeTransport`` will use the Shellstream protocol library
|
||||
(``substrate/shellstream/``) to establish a 3-way attested handshake
|
||||
with the target. The SAT (Shell Attestation Token) embedded in the
|
||||
Shellstream frame header is derived from the AC. See
|
||||
``SS-SPEC-0001 — Shellstream Protocol.md`` for the handshake flow.
|
||||
|
||||
Library: ``shellstream-py`` (future) or gRPC to Bascule proxy
|
||||
Protocol: Shellstream over TCP / QUIC
|
||||
Auth: AC passthrough — the AC IS the credential
|
||||
|
||||
Stubbed in this sprint — transport returns placeholder results.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from gsap_broker.connectors.session import SessionConnector, SessionTransport
|
||||
from gsap_broker.credentials.resolver import Credential, CredentialResolver
|
||||
|
||||
|
||||
class BasculeTransport(SessionTransport):
|
||||
"""Shellstream transport to a Bascule-governed endpoint.
|
||||
|
||||
Stubbed — actual Shellstream integration in a future sprint.
|
||||
"""
|
||||
|
||||
transport_id = "bascule"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._target = ""
|
||||
self._connected = False
|
||||
|
||||
async def connect(self, target: str, credential: Credential) -> None:
|
||||
# TODO: establish Shellstream connection to target via Bascule
|
||||
# proxy. The credential IS the AC (BasculeCredential).
|
||||
# Handshake: ATTEST-INIT → ATTEST-VERIFY → ATTEST-CONFIRM.
|
||||
self._target = target
|
||||
self._connected = True
|
||||
|
||||
async def execute(self, command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
||||
# TODO: send command via Shellstream, capture output.
|
||||
# Shellstream frames carry the SAT in the header for
|
||||
# per-frame attestation.
|
||||
if not self._connected:
|
||||
raise RuntimeError("Not connected")
|
||||
return {
|
||||
"stub": True,
|
||||
"transport": self.transport_id,
|
||||
"target": self._target,
|
||||
"command": command,
|
||||
"params": params or {},
|
||||
}
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
self._connected = False
|
||||
|
||||
async def is_alive(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
|
||||
class BasculeConnector(SessionConnector):
|
||||
"""Governed shell connector using Bascule/Shellstream."""
|
||||
|
||||
connector_id = "bascule"
|
||||
corpus_entry_cid = "sha256:bascule-connector-v1"
|
||||
credential_type = "bascule_ac"
|
||||
transport_class = BasculeTransport
|
||||
capability_mask = 0x7 # READ | PROPOSE | MUTATE
|
||||
declared_endpoints = ["shellstream://*"]
|
||||
accord_template = "governed-shell"
|
||||
gsap_required = True
|
||||
chronicle_enabled = True
|
||||
|
||||
def __init__(self, credential_resolver: CredentialResolver):
|
||||
super().__init__(credential_resolver)
|
||||
88
gsap_broker/connectors/powershell.py
Normal file
88
gsap_broker/connectors/powershell.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Copyright 2026 Guildhouse Dev
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""PowerShell connector — WinRM/PSRP sessions to Windows endpoints.
|
||||
|
||||
Uses Kerberos credentials from the CredentialResolver (acquired via
|
||||
Entra cloud trust or on-prem KDC) to establish a PSRP (PowerShell
|
||||
Remoting Protocol) session over WinRM/HTTPS.
|
||||
|
||||
Real integration:
|
||||
Library: ``pypsrp`` (well-maintained PSRP client)
|
||||
Protocol: PSRP over WinRM (HTTPS port 5986)
|
||||
Auth: Kerberos (from ``KerberosCredential``) or CredSSP
|
||||
Output: Structured PSObject results deserialized to dicts
|
||||
|
||||
The transport will:
|
||||
1. Create a ``pypsrp.wsman.WSMan`` connection with the Kerberos
|
||||
ticket from the credential.
|
||||
2. Open a ``pypsrp.powershell.PowerShell`` runspace.
|
||||
3. Execute commands via ``ps.add_script(command).invoke()``.
|
||||
4. Return deserialized PSObject results as Python dicts.
|
||||
5. Close the runspace and WSMan connection on disconnect.
|
||||
|
||||
Stubbed in this sprint — transport returns placeholder results.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from gsap_broker.connectors.session import SessionConnector, SessionTransport
|
||||
from gsap_broker.credentials.resolver import Credential, CredentialResolver
|
||||
|
||||
|
||||
class PowerShellTransport(SessionTransport):
|
||||
"""PSRP transport over WinRM/HTTPS.
|
||||
|
||||
Stubbed — actual pypsrp integration in a future sprint.
|
||||
"""
|
||||
|
||||
transport_id = "psrp"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._target = ""
|
||||
self._connected = False
|
||||
|
||||
async def connect(self, target: str, credential: Credential) -> None:
|
||||
# TODO: create pypsrp.wsman.WSMan with Kerberos auth from
|
||||
# credential.ticket. Target format: hostname:5986.
|
||||
self._target = target
|
||||
self._connected = True
|
||||
|
||||
async def execute(self, command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
||||
# TODO: execute PowerShell command via PSRP.
|
||||
# ps = PowerShell(wsman); ps.add_script(command).invoke()
|
||||
# Return structured PSObject results as dicts.
|
||||
if not self._connected:
|
||||
raise RuntimeError("Not connected")
|
||||
return {
|
||||
"stub": True,
|
||||
"transport": self.transport_id,
|
||||
"target": self._target,
|
||||
"command": command,
|
||||
"params": params or {},
|
||||
}
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
self._connected = False
|
||||
|
||||
async def is_alive(self) -> bool:
|
||||
return self._connected
|
||||
|
||||
|
||||
class PowerShellConnector(SessionConnector):
|
||||
"""Windows management via PowerShell Remoting."""
|
||||
|
||||
connector_id = "powershell"
|
||||
corpus_entry_cid = "sha256:powershell-connector-v1"
|
||||
credential_type = "kerberos"
|
||||
transport_class = PowerShellTransport
|
||||
capability_mask = 0x7 # READ | PROPOSE | MUTATE
|
||||
declared_endpoints = ["wsman://*:5986"]
|
||||
accord_template = "windows-management"
|
||||
gsap_required = True
|
||||
chronicle_enabled = True
|
||||
|
||||
def __init__(self, credential_resolver: CredentialResolver):
|
||||
super().__init__(credential_resolver)
|
||||
Loading…
Reference in a new issue