Skip to content

Repository files navigation

ota

k9s for Okta — a keyboard-driven terminal UI for exploring and auditing Okta Workforce Identity.

Release License Go version Platforms


ota lets IAM operators, security auditors, and SREs navigate Users, Groups, Group Rules, Policies, and System Logs without leaving the terminal. It brings the keyboard-first "press :, type a resource, drill down" workflow that k9s established for Kubernetes to Okta's identity model.

Status: v0.1.2 — read-only. Every action is a GET; ota cannot mutate your tenant. Write actions are planned for v0.2 — see Roadmap.

Contents · Install · Quick start · Configuration · Keyboard reference · Supported resources · Troubleshooting · Roadmap · Contributing


Why ota

Okta's Admin Console takes many clicks to correlate a user's group membership with their recent sign-in events, and ad-hoc curl | jq snippets lose context fast. ota collapses those loops into a few keystrokes:

Question In ota
Why can't alice@example.com reach this app? :users/aliceEnter → Groups tab
What sign-ins failed in the last 24h? :logsP → preset Failed Sign-ins (24h)
Is this Group Rule still valid? :grouprules → look for the red ⚠ INVALID badge

Install

Pick whichever fits your setup — all channels ship the same binary for macOS (Intel + Apple Silicon), Linux (amd64 + arm64), and Windows (amd64).

Homebrew

brew install tedilabs/tap/ota

Upgrade with brew upgrade ota. The tap also carries tfvault.

mise

Install through the github backend, which resolves release archives straight from GitHub:

mise use -g github:tedilabs/ota          # latest
mise use -g github:tedilabs/ota@0.1.2    # pinned

Or add it to a project's mise.toml:

[tools]
"github:tedilabs/ota" = "latest"

An aqua-registry entry is pending review. Once it merges, mise use -g aqua:tedilabs/ota will work as well and will additionally verify the release checksums — the same pair of backends tfvault offers.

Install script

Detects your OS/arch, verifies the SHA256 checksum against checksums.txt, and drops the binary in /usr/local/bin (falling back to ~/.local/bin when that isn't writable):

curl -fsSL https://raw.githubusercontent.com/tedilabs/ota/main/install.sh | sh

# pin a version, or choose the install directory
curl -fsSL https://raw.githubusercontent.com/tedilabs/ota/main/install.sh | sh -s -- --version v0.1.2
curl -fsSL https://raw.githubusercontent.com/tedilabs/ota/main/install.sh | sh -s -- --bin-dir ~/.local/bin

Docker

docker run --rm -it \
  -e OKTA_ORG_URL -e OKTA_API_TOKEN \
  ghcr.io/tedilabs/ota:latest

Multi-arch (linux/amd64, linux/arm64), distroless, runs as non-root. To reuse your profiles, mount the config directory and point XDG at it:

docker run --rm -it \
  -e OKTA_ORG_URL -e OKTA_API_TOKEN \
  -e XDG_CONFIG_HOME=/config \
  -v ~/.config/ota:/config/ota:ro \
  ghcr.io/tedilabs/ota:latest

Direct download

Grab a tarball from the releases page. Each release ships per-arch archives, a macOS universal binary (ota_<ver>_macos_all.tar.gz), checksums.txt, and SBOMs.

From source

go install github.com/tedilabs/ota/cmd/ota@latest   # needs Go 1.24+

go install builds without release ldflags, so ota --version reports dev. For a stamped build, clone and use the Makefile:

git clone https://github.com/tedilabs/ota.git && cd ota
make build && ./bin/ota --version

Quick start

1. Create a read-only API token

  1. In the Okta Admin Console, create a Read-Only Administrator account (Security → Administrators). No Okta tenant yet? Start with a free developer org.
  2. Sign in as that read-only admin, then go to Security → API → Tokens → Create Token.
  3. Name it ota-readonly and copy the value — Okta shows it only once.

Why a read-only admin? ota only issues GETs, but scoping the token to a read-only role means a leaked token still can't mutate your tenant. Tokens are never written to disk and are scrubbed from debug logs and panic traces.

2. Point ota at your org

export OKTA_ORG_URL="https://dev-NNNNNN.okta.com"   # or your custom domain
export OKTA_API_TOKEN="<token from step 1>"

Both are required. OKTA_ORG_URL accepts <org>.okta.com, <org>.oktapreview.com, and custom domains.

3. Run

ota

You land on the Users list. Press ? for context-aware help, : for the command palette, q to quit.

Connectivity trouble? ota --check probes the API once and prints a plain-text diagnostic without starting the TUI.

Command-line flags

Flag Purpose
--profile <name> Select a tenant profile from the config file
--config <path> Explicit config path (overrides XDG lookup)
--token-env <VAR> Read the token from a different env var
--check One-shot API diagnostic, no TUI
--poll-interval <sec> Override the Logs tail interval
--debug Write a debug log to ~/.cache/ota/debug.log
--version Print version and exit

Configuration

ota reads an optional YAML config from $XDG_CONFIG_HOME/ota/config.yaml (default ~/.config/ota/config.yaml). Environment variables alone are enough for a single tenant — the config file exists for multi-tenant setups and preference overrides.

Example ~/.config/ota/config.yaml
profiles:
  dev:
    org_url: "https://dev-123456.okta.com"
    api_token_env: "OKTA_API_TOKEN"        # env var holding the token
    default_log_filter: ""
  prod:
    org_url: "https://acme.okta.com"
    api_token_env: "OKTA_PROD_API_TOKEN"

ui:
  theme: "dark"                            # dark | high_contrast | monochrome
  pii_masking:
    enabled: true                          # phone/email masked by default
    default_unmask_on_copy: false
    logs_actor_email: false                # stricter compliance mode

keybindings:                               # override any key ID
  nav.down: "j"
  nav.up: "k"
  app.quit: "q"
  search.open: "/"

logs:
  poll_interval_seconds: 7                 # tail interval (5–10 recommended)

debug: false                               # writes ~/.cache/ota/debug.log

Select a profile at startup with --profile <name>. Keybinding IDs come from internal/keys/keys.go; the full catalogue lives in docs/CONVENTIONS.md §7 and docs/TUI_DESIGN.md §3.


Keyboard reference

Defaults are k9s-compatible with Vim navigation. Press ? in any screen for the live, context-aware list.

Global

Key Action
: Command palette
/ Incremental search (lists only)
? Help modal
Esc Cancel current mode/modal
q Close screen / quit (with confirm)
Ctrl-c Soft quit; double-tap to force-exit
Ctrl-l Force redraw (after a tmux resize)

Navigation

Key Action
j k / Down / up one row
h l / Tab or column left / right
gg / G Top / bottom of list
Ctrl-d Ctrl-u Half-page down / up
Ctrl-f Ctrl-b Full-page down / up
Enter or d Open detail (all attributes, incl. [Raw] tab)
Esc Back to the list
Tab Shift-Tab Cycle detail tabs

Sort

Shift+<letter> cycles a column off → ascending → descending → off. The active column shows / beside its header.

Key Sorts by Available in
Shift+S STATUS (operational rank — INVALID/LOCKED_OUT first) Users, Group Rules
Shift+N NAME (alphabetical) Users, Groups, Group Rules
Shift+L LAST LOGIN Users
Shift+C CREATED / CHANGED Users

Inspect & copy

Key Action
R Refresh current resource (invalidates cache)
r Detail: toggle [Raw] JSON tab · Lists: rich ↔ raw (Policies, Logs)
y / yy / yf Copy selection / row / focused field to clipboard
o Open the Admin Console link in a browser
e Expand / collapse detail sections (e.g. Factor IDs)
s / f Logs: toggle tail mode / auto-follow

Command palette

Command Effect
:users :user :u Users list
:groups :group :g Groups list
:grouprules :rules :gr (+ hyphen/underscore variants) Group Rules list
:policies [TYPE] Policy type selector, or jump straight to e.g. OKTA_SIGN_ON
:logs :l Logs search / tail
:search <SCIM-expr> Server-side search (Users, Groups)
:filter <SCIM-expr> Server-side filter (Groups, Apps, Logs)
:unmask <field> / :mask Reveal / re-hide masked PII for this session
:raw Toggle raw JSON in detail screens
:refresh Drop cache and reload
:about App, token, and rate-limit summary
:errors Session error history
:debug open Print the debug log path
:help :? · :quit :q Help · Quit

:search on Users is eventually consistent — freshly created users can take minutes to surface. Use :filter when you need read-after-write.

Full key map and screen catalogue: docs/TUI_DESIGN.md §3 & §4.


Supported resources

All read-only in v0.1.2.

Resource List Detail Highlights
Users ✅ 6 tabs Profile (fixed + custom fields), Credentials, Timestamps, Groups, Factors (PII masked), Recent activity
Groups ✅ 4 tabs Type icons for OKTA_GROUP / APP_GROUP / BUILT_IN; RULE / SYS / LARGE badges; Members tab loads progressively (Esc stops)
Group Rules ACTIVE / INACTIVE / INVALID colour-coded, INVALID counter banner, expression in monospace
Policies ✅ per type All 7 types listed; rich rendering for OKTA_SIGN_ON, ACCESS_POLICY, PASSWORD, MFA_ENROLL; raw JSON for the other three
System Logs ✅ + tail Adaptive ~7s polling (stretches to 15s on low-quota tenants), hole-free resume after a 429, 5 filter presets

Authentication: Okta SSWS API tokens via environment variables, optionally mapped per-profile with api_token_env.


Known limitations

Tracked in PRD §11.3.1:

  • Token input is environment-only — interactive prompt lands in v0.2.
  • No runtime :profile switch — choose with --profile and relaunch.
  • :ratelimit / :healthcheck modals are partial, and the [RL] header badge may be missing in some builds.
  • Config file permissions aren't validated — ota stores no tokens, but a warn-only 0600 check is queued.
  • PROFILE_ENROLLMENT, POST_AUTH_SESSION, IDP_DISCOVERY policies render as raw JSON only — press r.
  • Detail runs inline inside the list model; the routing refactor is deferred to v0.2 with no user-visible change.
  • Per-field M! unmask isn't exposed — the Raw tab masks at projection time and annotates masked lines with # masked.

Troubleshooting

Symptom What's happening
E0000004 / 401 Token invalid or revoked — rotate it in Security → API → Tokens
E0000006 / 403 Token's role lacks read scope for that resource; check :about
E0000007 / 404 Resource was deleted by another admin; press R to refresh
429 Rate limited ota auto-pauses tail polling and resumes from the last published timestamp — no events are lost
Garbled output after a tmux resize Press Ctrl-l to force a redraw
Anything else Run :errors in-app, or start with --debug and read ~/.cache/ota/debug.log

Can't connect at all? ota --check prints a one-shot diagnostic without the TUI.


Roadmap

v0.1.x — config permission warning, rate-limit numeric display + :ratelimit modal.

v0.2 (targeting Q3 2026)

  • Apps resource (list, detail, User → Apps tab)
  • Interactive token prompt and runtime :profile switching
  • First write actions, in ascending order of blast radius:
    1. Group static member add / remove
    2. User lifecycle — unlock, unsuspend, activate, deactivate
    3. Group Rule activate / deactivate (double-confirm + impact estimate)
  • Rich renderers for the remaining 3 policy types
  • OAuth 2.0 Service App (Private Key JWT) auth
  • :unmask <field> selective PII reveal

v0.3+ — custom view DSL, Event Hook pseudo-streaming, shareable URI scheme.


Contributing

Issues and PRs welcome at https://github.com/tedilabs/ota/issues. Conventions, architecture, and testing rules live in docs/; release mechanics are in docs/RELEASING.md.

When filing a bug, please include:

  • ota --version output
  • Okta tenant type (Developer Free / Production / Preview)
  • Steps to reproduce, plus expected vs. observed behaviour
  • Relevant lines from ~/.cache/ota/debug.log (tokens are auto-redacted)

License

Apache License 2.0 — see LICENSE.

Acknowledgements

About

♥️ k9s for Okta — a Go/Bubbletea TUI to inspect Okta admin resources

Topics

Resources

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages