Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,8 @@ Pulumi.*.yaml

# Ignore Pulumi local backend state files.
.pulumi/


# Output files
# -----------------------------------------------------------------------------
output/
13 changes: 13 additions & 0 deletions packages/python/langsmith-network/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "langsmith-network"
version = "0.1.0"
description = "LangSmith Cloud NAT gateway IP addresses for firewall rules"
requires-python = ">=3.12,<3.13"
dependencies = []

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/langsmith_network"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""LangSmith Cloud NAT gateway IP addresses for firewall rules."""

from .langsmith import EU, LANGSMITH, US

__all__ = [
"EU",
"LANGSMITH",
"US",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""LangSmith Cloud NAT gateway IP addresses.

Source: https://docs.langchain.com/langsmith/deploy-to-cloud#allowlist-ip-addresses
"""

US: tuple[str, ...] = (
"34.9.99.224",
"34.19.34.50",
"34.19.93.202",
"34.31.121.70",
"34.41.178.137",
"34.59.244.194",
"34.68.27.146",
"34.82.222.17",
"34.121.166.52",
"34.123.151.210",
"34.135.61.140",
"34.145.102.123",
"34.169.45.153",
"34.169.88.30",
"35.197.29.146",
"35.227.171.135",
)

EU: tuple[str, ...] = (
"34.13.244.114",
"34.32.141.108",
"34.32.145.240",
"34.32.180.189",
"34.34.69.108",
"34.90.157.44",
"34.90.213.236",
"34.141.242.180",
)

LANGSMITH: tuple[str, ...] = US + EU
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ members = [
"tools/python/langsmith-hosting",
"tools/python/pulumi-utils",
"packages/python/langsmith-client",
"packages/python/langsmith-network",
"packages/python/azure-ai",
]

Expand Down
3 changes: 2 additions & 1 deletion tools/python/langsmith-hosting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Pulumi project that provisions AWS infrastructure for LangSmith Hybrid.

## Prerequisites

- AWS CLI configured with a named profile (set `awsProfile` in your stack config)
- AWS CLI configured with an appropriate profile
- [uv](https://docs.astral.sh/uv/) installed
- Pulumi CLI installed

Expand All @@ -39,3 +39,4 @@ Stack configuration lives in `Pulumi.dev.yaml`. Key settings:
| `postgresInstanceClass` | RDS instance class | `db.t3.medium` |
| `redisNodeType` | ElastiCache node type | `cache.t3.micro` |
| `s3BucketPrefix` | S3 bucket name prefix | `langsmith` |
| `extraPublicAccessCidrs` | Comma-separated CIDRs to add to the EKS API server allowlist | _(none)_ |
26 changes: 19 additions & 7 deletions tools/python/langsmith-hosting/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ langsmith-hosting/
├── pyproject.toml # Dependencies (pulumi, pulumi-aws, pulumi-eks, ...)
├── README.md
├── docs/
│ └── architecture.md # This file
│ ├── architecture.md # This file
│ └── cidr-entry-points.md # CIDR plugin discovery mechanism
└── src/
└── langsmith_hosting/
├── __init__.py
├── __main__.py # Entry point: wires all modules, exports outputs
├── cidrs.py # CIDR utilities (get_cidrs, collapse_cidrs)
├── config.py # Typed config loading from Pulumi stack config
├── constants.py # PROJECT_NAME, TAGS
├── constants.py # PROJECT_NAME, get_tags()
├── vpc.py # VPC + subnets
├── eks.py # EKS cluster + node group + addons
├── postgres.py # RDS PostgreSQL
Expand Down Expand Up @@ -52,11 +54,21 @@ Orchestrates all modules in order:
1. Loads `.env` and Pulumi stack config via `config.py`
2. Gets AWS caller identity and region
3. Creates VPC
4. Creates EKS cluster (depends on VPC)
5. Creates PostgreSQL (depends on VPC + random password)
6. Creates Redis (depends on VPC)
7. Creates S3 bucket (depends on VPC)
8. Exports stack outputs
4. Builds the EKS API server CIDR allowlist (LangSmith IPs + [entry point plugins](cidr-entry-points.md) + manual overrides)
5. Creates EKS cluster (depends on VPC)
6. Creates PostgreSQL (depends on VPC + random password)
7. Creates Redis (depends on VPC)
8. Creates S3 bucket (depends on VPC)
9. Creates data plane (listener + KEDA + langgraph-dataplane Helm chart)
10. Exports stack outputs

### `cidrs.py` -- CIDR Utilities

Helper functions for IP/CIDR manipulation:

- `get_cidrs()` -- appends `/32` (IPv4) or `/128` (IPv6) to bare IP addresses
- `collapse_cidrs()` -- best-effort collapse of a CIDR list toward a target count (AWS EKS allows at most 40 public access CIDRs)
- `AWS_EKS_MAX_PUBLIC_ACCESS_CIDRS` -- the 40-entry limit constant

### `config.py` -- Configuration

Expand Down
94 changes: 94 additions & 0 deletions tools/python/langsmith-hosting/docs/cidr-entry-points.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# CIDR Entry Points

The EKS API server allowlist is built from three sources, merged and
deduplicated at deploy time:

```python
_all_cidrs = list(
dict.fromkeys(
get_cidrs(LANGSMITH) # 1. Built-in LangSmith IPs
+ tuple(_org_cidrs) # 2. Entry point plugins
+ cfg.extra_public_access_cidrs # 3. Manual overrides
)
)
```

Source 2 uses [Python entry points](https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata),
the PyPA-standard plugin discovery mechanism (`importlib.metadata`). Any
installed package can provide CIDRs by declaring an entry point in the
`langsmith_hosting.cidrs` group.

## How it works

### Consumer side (langsmith-hosting)

`__main__.py` discovers all registered CIDR providers at runtime:

```python
from importlib.metadata import entry_points

_org_cidrs: list[str] = []
for _ep in entry_points(group="langsmith_hosting.cidrs"):
_org_cidrs.extend(_ep.load())
```

`entry_points(group=...)` scans every installed package's metadata for
entries in that group. `ep.load()` performs the import and attribute
lookup, returning the CIDR tuple.

### Provider side (any package)

A provider declares entry points in its `pyproject.toml`:

```toml
[project.entry-points."langsmith_hosting.cidrs"]
my-corp = "my_network.corporate:CORPORATE_CIDRS"
```

Each entry follows the format `name = "module.path:ATTRIBUTE"`:

| Part | Meaning |
| --- | --- |
| `my-corp` | Human-readable name (used for inspection, not code) |
| `my_network.corporate` | Python module to import |
| `CORPORATE_CIDRS` | Attribute on that module — must be an iterable of CIDR strings |

### Concrete example

An internal networking package could declare:

```toml
[project.entry-points."langsmith_hosting.cidrs"]
corporate = "my_network.corporate:CORPORATE_CIDRS"
```

When that package is installed (e.g., via `uv sync --all-packages`), its
CIDRs are automatically discovered and included. When it is absent,
`entry_points()` returns nothing and only the LangSmith IPs + manual
overrides are used.

## Inspecting registered entry points

```bash
uv run python -c "
from importlib.metadata import entry_points
for ep in entry_points(group='langsmith_hosting.cidrs'):
print(f'{ep.name}: {ep.load()}')
"
```

## Adding a new CIDR provider

1. Create a Python package with a module that exports a tuple of CIDR
strings (e.g., `my_network/firewalls.py` with `SCANNER_IPS`).
2. Add the entry point to the package's `pyproject.toml`:

```toml
[project.entry-points."langsmith_hosting.cidrs"]
scanner = "my_network.firewalls:SCANNER_IPS"
```

3. Install the package in the same environment as `langsmith-hosting`
(or add it to the uv workspace).
4. Run `uv sync --all-packages` to register the entry point.
5. `pulumi preview` will now include the new CIDRs.
Loading
Loading