Skip to content
Closed
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
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Changelog

All notable changes to this project are documented here. The format loosely
follows [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

### Added
- **Import Microsoft 365 / Entra ID (Azure AD) credentials from the login helper.**
Three converging paths now load a `CLIProxyAPI_*.json` file produced by
`kiro-login-helper.py`, all funnelling through one `importOne` core so the
persisted account is identical to an interactive Enterprise SSO login:
- `apiImportCredentials` (`POST /admin/api/auth/credentials`) now understands the
`external_idp` auth method and accepts the helper's native snake_case keys
(`token_endpoint`, `issuer_url`, `scopes`, `profile_arn`) in addition to the
existing camelCase payload.
- New `POST /admin/api/auth/import-cli-json` endpoint ingests a single helper
object, a JSON array, a `{ "files": [...] }` / `{ "accounts": [...] }` wrapper,
or raw text with several objects, returning per-item results.
- `KIRO_IMPORT_WATCH` / `KIRO_IMPORT_DIR` zero-touch drop-folder watcher
(`data/imports/`): valid files are imported within ~15s through `config.AddAccount`
then moved to `processed/`, invalid ones to `failed/` with a `.error.txt` sidecar.
Enabled by default in `docker-compose.yml`.
- Admin panel file picker for uploading helper JSON; `app.js` credential parsing now
maps both snake_case and camelCase.
- `testdata/CLIProxyAPI_sample_external_idp.json` sanitized fixture.
- **Import directly from the Kiro IDE cache (no browser, no helper script).** New
`POST /admin/api/auth/import-ide-cache` endpoint and an admin-panel button read the
credential the Kiro IDE already cached on the host
(`~/.aws/sso/cache/kiro-auth-token.json`, overridable via `KIRO_IDE_CACHE` or a `path`
body field) and import it through the same `importOne` core. The cache's camelCase keys
map straight onto the existing decoder; its stale `expiresAt` is ignored in favor of the
mandatory refresh.
- **Configurable listen port/host.** New `-port` / `-host` CLI flags and `PORT` /
`HOST` env overrides (precedence: flag > env > `config.json`), so the proxy can run
on a port other than `8080` without editing the config. `docker-compose.yml` honors
`KIRO_PORT` for the published host port (`KIRO_PORT=9090 docker compose up -d`).

### Fixed
- `external_idp` imports previously returned `400 "external IdP refresh requires
clientId and tokenEndpoint"` because the import endpoint dropped `tokenEndpoint`,
`issuerUrl`, `scopes`, and `profileArn`. The refresh-before-import step now carries
the external IdP material so the refresh against the IdP token endpoint succeeds,
and an `external_idp` import that omits `token_endpoint`/`client_id` is rejected
up front with an actionable message instead of an opaque refresh failure.

### Security
- The account email is stored as a label only; the password is never persisted or
sent upstream. Microsoft 365 tenants enforce MFA / Conditional Access, so a headless
ROPC password grant is not a supported auth path — the interactive helper (browser
PKCE) remains the canonical credential-mint path.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ git clone https://github.com/zsecducna/Kiro-Go.git
cd Kiro-Go
go build -o kiro-go .
./kiro-go

# Run on a different port (flag > PORT env > config.json):
./kiro-go -port 9090
# or: PORT=9090 ./kiro-go
```

### Deploy on Zeabur
Expand Down Expand Up @@ -106,12 +110,71 @@ For users in restricted network regions, configure an outbound proxy in the admi

The setting takes effect immediately without restarting.

## Importing Microsoft 365 / Entra ID (Azure AD) credentials

Enterprise SSO (Microsoft 365 / Entra ID) accounts are neither AWS Builder ID nor
IAM Identity Center accounts, so they are minted through the interactive browser
sign-in helper `kiro-login-helper.py`, which writes a `CLIProxyAPI_<user>.json`
credential file (`auth_method: external_idp`). There are three ways to load that file:

1. **Paste / upload in the admin panel.** Add Account → Credentials JSON (or the
Enterprise SSO card's file picker) accepts the helper's native `CLIProxyAPI_*.json`
verbatim — snake_case keys (`token_endpoint`, `issuer_url`, `scopes`, `profile_arn`)
are understood.

2. **API.** `POST /admin/api/auth/import-cli-json` accepts a single helper object, a
JSON array, a `{ "files": ["<json>", ...] }` / `{ "accounts": [...] }` wrapper, or
raw text with several objects. It returns per-item results.

```bash
curl -X POST http://localhost:8080/admin/api/auth/import-cli-json \
-H "X-Admin-Password: $ADMIN_PASSWORD" \
--data-binary @CLIProxyAPI_user.json
```

3. **Zero-touch drop folder (Docker).** With `KIRO_IMPORT_WATCH=1` (set by default in
`docker-compose.yml`), any `CLIProxyAPI_*.json` placed in `data/imports/` is imported
within ~15s, then moved to `data/imports/processed/` (or `failed/` with a `.error.txt`
sidecar). Imports go through the same persisted path the running server owns, so they
never race the in-memory config.

4. **Import from the Kiro IDE cache (no browser, no helper).** If the Kiro IDE is already
signed in on the same host as the proxy, it keeps a live credential at
`~/.aws/sso/cache/kiro-auth-token.json`. The admin panel's Enterprise SSO card has an
**Import from Kiro IDE (this host)** button, or call the API directly:

```bash
curl -X POST http://localhost:8080/admin/api/auth/import-ide-cache \
-H "X-Admin-Password: $ADMIN_PASSWORD"
# custom location: -d '{"path":"/path/to/kiro-auth-token.json"}'
```

The proxy reads the file **server-side**, so this works only when the IDE and the proxy
share a host — or, in Docker, when the host AWS SSO cache directory is mounted into the
container. The Compose file does this portably for Linux/macOS with
`${HOME}/.aws/sso/cache:/host-aws-sso-cache:ro`; override `KIRO_AWS_SSO_CACHE_DIR` if
your Kiro IDE uses a different location. The cache's stale `expiresAt` is ignored: the
import performs a mandatory refresh, so the persisted expiry always comes from a fresh
upstream response.

> The account email is stored as a label only. The password is **never** persisted or
> sent upstream — Microsoft 365 tenants enforce MFA / Conditional Access, so a headless
> password (ROPC) grant is not a reliable auth path. Use the interactive helper to mint
> the credential, then import the JSON.

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `CONFIG_PATH` | Config file path | `data/config.json` |
| `ADMIN_PASSWORD` | Admin panel password (overrides config) | - |
| `PORT` | HTTP listen port (overrides config; `-port` flag wins over this) | `8080` |
| `HOST` | HTTP bind host (overrides config; `-host` flag wins over this) | `127.0.0.1` |
| `KIRO_IMPORT_WATCH` | Enable the `data/imports/` auto-ingest watcher (`1`/`true`) | off (on in Docker) |
| `KIRO_IMPORT_DIR` | Directory the watcher scans for `CLIProxyAPI_*.json` | `data/imports` |
| `KIRO_IDE_CACHE` | Path to the Kiro IDE credential cache for `import-ide-cache` | `~/.aws/sso/cache/kiro-auth-token.json` (Docker: `/host-aws-sso-cache/kiro-auth-token.json`) |
| `KIRO_AWS_SSO_CACHE_DIR` | Host AWS SSO cache directory mounted by Docker Compose for IDE-cache import | `$HOME/.aws/sso/cache` |
| `KIRO_PROFILE_REGIONS` | Comma-separated fallback regions for external_idp profile probing | `us-east-1,eu-central-1` |

## Contributing

Expand Down
17 changes: 17 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,23 @@ func SetPassword(password string) {
cfg.Password = password
}

// SetPort overrides the HTTP listen port in memory (does not persist). Used by
// the -port CLI flag / PORT env override so an operator can run on a port other
// than the configured one without editing config.json.
func SetPort(port int) {
cfgLock.Lock()
defer cfgLock.Unlock()
cfg.Port = port
}

// SetHost overrides the HTTP bind host in memory (does not persist). Used by the
// -host CLI flag / HOST env override.
func SetHost(host string) {
cfgLock.Lock()
defer cfgLock.Unlock()
cfg.Host = host
}

// GetConfigDir returns the directory containing the config JSON file.
// Useful for sibling state (e.g. stored Responses, caches) that should live
// alongside the configuration file.
Expand Down
28 changes: 25 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: '3.8'

services:
kiro-go:
build: .
ports:
- "8080:8080"
# Host port is overridable: `KIRO_PORT=9090 docker compose up -d` publishes
# 9090 on the host. The container always listens on 8080 internally.
- "${KIRO_PORT:-8080}:8080"
# Enterprise SSO (Microsoft 365) callback. The hosted-portal sign-in
# redirects the browser to localhost:3128, so the operator must run that
# browser ON THE DOCKER HOST. Publishing host-side to 127.0.0.1 only keeps
Expand All @@ -13,8 +13,30 @@ services:
- "127.0.0.1:3128:3128"
volumes:
- ./data:/app/data
# Zero-touch credential drop folder. Any CLIProxyAPI_*.json minted by
# kiro-login-helper.py and dropped in ./data/imports is auto-imported within
# ~15s (then moved to ./data/imports/processed). It is a subpath of the data
# mount above, so no extra mount is strictly required — kept explicit for clarity.
- ./data/imports:/app/data/imports
# Let the Dockerized app import the Kiro IDE credential cached on the host.
# The app runs as root in the container, so its default ~/.aws path would be
# /root/.aws/...; mount the host user's AWS SSO cache directory explicitly.
# Directory mount is portable across Linux/macOS and lets the container start
# even when the cache file has not been created yet.
- ${KIRO_AWS_SSO_CACHE_DIR:-${HOME}/.aws/sso/cache}:/host-aws-sso-cache:ro
environment:
- CONFIG_PATH=/app/data/config.json
- KIRO_IDE_CACHE=/host-aws-sso-cache/kiro-auth-token.json
# Force the app's in-container listen port to match the published
# container-side port above. This prevents a stale data/config.json port
# (for example 8081) from making `localhost:8080` unreachable.
- PORT=8080
- HOST=0.0.0.0
# Enable the auto-ingest watcher (off by default on a bare build). Drop a
# CLIProxyAPI_*.json into ./data/imports and the account appears with no
# restart and no manual API call. Imports go through the same persisted path
# the live server owns, so they never race the in-memory config.
- KIRO_IMPORT_WATCH=1
# Bind host for the Enterprise SSO callback listener INSIDE the container.
# Default (unset) is loopback-only (127.0.0.1/::1), which the published port
# above cannot reach; 0.0.0.0 lets the forwarded connection land. The callback
Expand Down
Loading