Metadata-Version: 2.4
Name: hush-am-sdk
Version: 0.1.0
Summary: Python SDK for Hush secret access via SPIFFE mTLS
Author-email: Hush Security <support@hush.security>
License-Expression: Apache-2.0
Project-URL: Homepage, https://hush.security
Project-URL: Repository, https://github.com/hushsecurity/hush-am-sdk-python
Project-URL: Issues, https://github.com/hushsecurity/hush-am-sdk-python/issues
Keywords: hush,secrets,spiffe,mtls,sdk,access-manager
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: spiffe>=0.2.0
Requires-Dist: spiffe-tls>=0.3.1
Requires-Dist: httpx>=0.27.0
Requires-Dist: cryptography>=41.0
Requires-Dist: pyOpenSSL>=23.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pylint>=4.0.4; extra == "dev"
Requires-Dist: ruff>=0.15.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# hush-am-sdk-python

Python SDK for fetching secrets from the Hush access-manager via SPIFFE mTLS.

## Installation

```bash
pip install hush-am-sdk
```

## Configuration

The client reads the following environment variables (all populated by the
mufasa injector at runtime — there are no constructor overrides):

| Variable | Default | Description |
| --- | --- | --- |
| `_HUSH_INJECTOR_SERVER_ADDRESS` | `hush-am-access-manager.hush-security.svc:8743` | Access-manager address (`https://` is added if no scheme is present) |
| `_HUSH_INJECTOR_SPIRE_AGENT_SOCKET_PATH` | `unix:///tmp/spire-agent/public/api.sock` | SPIRE agent socket |
| `_HUSH_INJECTOR_TRUST_DOMAIN` | _(required)_ | SPIFFE trust domain. Used to validate the server's identity as `spiffe://<trust-domain>/hush/simba/server` |
| `_HUSH_INJECTOR_POLICY_SNAPSHOT` | _(required to call `get_secret`)_ | Base64 policy snapshot |

## Usage

```python
from hush import HushClient

with HushClient() as hush:
    # Fetch a secret's full key-value map
    creds = hush.get_secret("user-a-postgres")
    print(creds["username"], creds["password"])

    # Fetch a single field
    password = hush.get_secret_field("user-a-postgres", "password")
```

Module-level convenience helpers backed by a singleton client are also available:

```python
from hush import get_secret, get_secret_field

creds = get_secret("user-a-postgres")
password = get_secret_field("user-a-postgres", "password")
```

Pass an optional bearer token for user-claims filtering:

```python
hush.get_secret("user-a-postgres", token=user_jwt)
```

## Error handling

All SDK errors derive from `HushError`. Catch the base class for a generic
fallback, or the specific subclasses where the caller can do something useful:

| Exception | Raised when |
| --- | --- |
| `HushConfigError` | A required `_HUSH_INJECTOR_*` env var is missing |
| `HushSpiffeError` | SPIRE Workload API unreachable or no SVID issued |
| `HushAuthError` | mTLS handshake failed or server SPIFFE ID mismatch |
| `HushNetworkError` | Connection / timeout reaching the access-manager |
| `HushSecretNotFoundError` | Secret missing, or caller's SPIFFE ID has no policy granting access (simba returns 404 for both) |
| `HushFieldNotFoundError` | Secret exists but the requested field does not |
| `HushBadRequestError` | Server returned 400 — deployment misconfiguration, not caller-recoverable |
| `HushServerError` | Server returned 5xx; `.status_code` and `.message` available |

```python
from hush import HushClient, HushSecretNotFoundError, HushError

try:
    with HushClient() as hush:
        creds = hush.get_secret("user-a-postgres")
except HushSecretNotFoundError:
    ...  # secret missing or no access
except HushError as e:
    ...  # any other SDK failure
```

## Running the example

`examples/get_postgres_creds.py` shows a minimal end-to-end use of the SDK.
It needs a Hush-instrumented runtime to actually fetch a secret — it cannot
run from your laptop because there is no local SPIRE agent.

**Required runtime conditions:**

1. A SPIRE agent reachable at `$_HUSH_INJECTOR_SPIRE_AGENT_SOCKET_PATH`
   (provided by your cluster's SPIRE deployment).
2. The mufasa admission controller has injected
   `$_HUSH_INJECTOR_POLICY_SNAPSHOT` into the pod — you do not set this
   manually.
3. `$_HUSH_INJECTOR_TRUST_DOMAIN` set to your deployment's trust domain.
4. A Hush policy named `postgres_creds` attached to your workload's SPIFFE
   ID, with `username`, `password`, and `host` fields.

**Steps inside such a pod:**

```bash
# 1. Install the SDK
pip install -e /path/to/hush-am-sdk-python

# 2. Confirm the runtime env is populated (the injector usually does this)
echo $_HUSH_INJECTOR_TRUST_DOMAIN
echo $_HUSH_INJECTOR_SPIRE_AGENT_SOCKET_PATH
echo $_HUSH_INJECTOR_POLICY_SNAPSHOT

# 3. Run
python examples/get_postgres_creds.py
```

To try it out without standing up your own infrastructure, deploy as a
workload modeled on `goat-apps/apps/hush-agent-demo/k8s/deployment.yaml` —
the same admission-controller annotations, with the container running this
script.

## Build & publish

The `build` and `publish` Makefile targets produce a wheel and upload it to
public PyPI:

```bash
# Build sdist + wheel into dist/
make build

# Build, then publish to PyPI (requires PyPI credentials in ~/.pypirc
# or TWINE_USERNAME / TWINE_PASSWORD env vars)
make publish
```

CI publishes automatically on `v*` tags via PyPI trusted publishing — see
`.github/workflows/publish.yml`.
