Skip to content

oauth encryption#84

Merged
jaxxstorm merged 6 commits intomainfrom
oauth_encryption
Apr 15, 2026
Merged

oauth encryption#84
jaxxstorm merged 6 commits intomainfrom
oauth_encryption

Conversation

@jaxxstorm
Copy link
Copy Markdown
Owner

  • feat(config): add oauth-backed profiles and age secret encryption
  • feat(config): add age key path and interactive profile setup
  • feat(config): add interactive setup and age key reuse

Copilot AI review requested due to automatic review settings April 15, 2026 18:52
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d4324f0da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/tscli/config/setup/cli.go Outdated
Comment thread pkg/config/profiles.go
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OAuth-backed authentication and AGE-based encryption for persisted config secrets, plus interactive setup flows, to make tscli usable without long-lived API keys while improving credential-at-rest security.

Changes:

  • Add AGE encryption config + encrypted sibling fields for profile secrets, including encryption setup and interactive config setup flows.
  • Expand runtime auth to support OAuth client-credentials exchange for general API requests (not just lifecycle commands).
  • Update CLI/docs/tests for new commands (config encryption setup, config setup) and renamed profile command (config profiles set).

Reviewed changes

Copilot reviewed 55 out of 56 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
test/docs/docs_test.go Adds assertions that docs mention encryption + OAuth profile concepts.
test/cli/testdata/leaf_commands.txt Updates expected leaf commands to include new config commands and rename profiles upsert -> profiles set.
test/cli/test_harness_test.go Adds CLI execution helpers that can provide stdin input to commands.
test/cli/persistent_prerun_runtime_config_test.go Adds integration coverage for OAuth profiles and decrypting encrypted profiles at runtime.
test/cli/example_output_test.go Adds example coverage for new setup/encryption commands and input-driven examples.
test/cli/config_profiles_integration_test.go Expands integration coverage for encryption setup, interactive setup, encrypted persistence, and delete semantics.
pkg/tscli/client.go Adds OAuth bearer-token transport for tsapi client when API key is absent.
pkg/oauth/exchange.go Adds ExchangeClientCredentialsAtURL helper to support configurable token endpoints.
pkg/config/profiles_test.go Updates deletion behavior tests and adds promotion test when deleting active profile.
pkg/config/profiles.go Adds encrypted secret fields, decryption during resolution, command-auth resolver, and active-profile delete semantics.
pkg/config/encryption_test.go Adds unit tests for AGE config validation, encrypt/decrypt helpers, identity inspection, and path expansion.
pkg/config/encryption.go Implements AGE config loading/validation, encryption/decryption, identity resolution, and persistence helpers.
pkg/config/config.go Canonicalizes persisted encryption settings by removing empty keys/blocks.
openspec/specs/multi-tailnet-config-profiles/spec.md Updates spec for encrypted secret fields and OAuth-backed API auth behavior.
openspec/specs/interactive-config-setup/spec.md Adds spec for interactive config setup onboarding/management flow.
openspec/specs/config-secret-encryption/spec.md Adds spec for AGE-based secret encryption and setup workflows.
openspec/specs/config-and-auth-documentation/spec.md Updates documentation requirements to include encryption and OAuth-backed API usage.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/tasks.md Adds archived change plan/tasks for interactive encryption setup improvements.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/specs/interactive-config-setup/spec.md Archives modified interactive setup requirements.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/specs/config-secret-encryption/spec.md Archives modified encryption requirements.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/proposal.md Archives proposal for interactive encryption improvements.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/design.md Archives design notes for interactive encryption improvements.
openspec/changes/archive/2026-04-15-improve-interactive-encryption-setup/.openspec.yaml Adds archive metadata.
openspec/changes/archive/2026-04-15-improve-config-setup/tasks.md Adds archived tasks for initial config setup flow.
openspec/changes/archive/2026-04-15-improve-config-setup/specs/multi-tailnet-config-profiles/spec.md Archives modified multi-tailnet profile requirements.
openspec/changes/archive/2026-04-15-improve-config-setup/specs/interactive-config-setup/spec.md Archives added interactive setup requirements.
openspec/changes/archive/2026-04-15-improve-config-setup/proposal.md Archives proposal for config setup flow.
openspec/changes/archive/2026-04-15-improve-config-setup/design.md Archives design for config setup flow.
openspec/changes/archive/2026-04-15-improve-config-setup/.openspec.yaml Adds archive metadata.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/tasks.md Archives tasks for OAuth + encryption feature set.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/specs/multi-tailnet-config-profiles/spec.md Archives modified requirements for OAuth-backed API usage + encryption.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/specs/config-secret-encryption/spec.md Archives added encryption requirements.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/specs/config-and-auth-documentation/spec.md Archives modified documentation requirements.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/proposal.md Archives proposal for OAuth + encryption.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/design.md Archives design for OAuth + encryption.
openspec/changes/archive/2026-04-15-add-oauth-credentials-and-config-encryption/.openspec.yaml Adds archive metadata.
internal/cli/root.go Switches prerun config validation to new command-auth resolver.
go.sum Adds new module sums for AGE and Bubble Tea dependencies.
go.mod Adds filippo.io/age and related indirect deps.
docs/configuration.md Documents encryption keys, encrypted secret fields, and updated profile commands.
docs/authentication.md Documents OAuth-backed API auth and optional AGE encryption workflow.
docs/commands/tscli_config_profiles_set.md Adds generated docs for renamed config profiles set.
docs/commands/tscli_config_profiles.md Updates command list to reference set instead of upsert.
docs/commands/tscli_config_encryption_setup.md Adds generated docs for config encryption setup.
docs/commands/tscli_config_encryption.md Adds generated docs for config encryption command group.
docs/commands/tscli_config.md Adds link to config encryption command group.
docs/commands/_sidebar.md Updates sidebar links for new/renamed commands.
docs/commands/README.md Updates generated README links for new/renamed commands.
cmd/tscli/config/setup/cli_test.go Adds unit tests for setup model behavior and key path handling.
cmd/tscli/config/setup/cli.go Implements interactive tscli config setup (Bubble Tea + prompt fallback) including encryption provisioning.
cmd/tscli/config/profiles/upsert/cli.go Renames command to set and adds interactive prompting for missing auth values.
cmd/tscli/config/profiles/deleteprofile/cli.go Updates delete command description to reflect new delete semantics.
cmd/tscli/config/encryption/setup/cli.go Implements tscli config encryption setup with path/env/command private key sourcing and reuse detection.
cmd/tscli/config/encryption/cli.go Adds config encryption command group.
cmd/tscli/config/cli.go Registers config setup and config encryption under the config command group.
README.md Updates examples to use config profiles set instead of upsert.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/cli/example_output_test.go Outdated
Comment thread cmd/tscli/config/profiles/upsert/cli.go
Comment thread docs/configuration.md Outdated
Comment on lines +31 to +38
if strings.TrimSpace(privateKeySource) == "" {
fmt.Fprintln(cmd.OutOrStdout(), "Private key source [path|env|command]: ")
value, err := reader.ReadString('\n')
if err != nil {
return err
}
privateKeySource = strings.TrimSpace(value)
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prompt uses Fprintln, which prints a newline before reading input. That makes the “prompt” behave like a message rather than an inline prompt (cursor moves to the next line) and is inconsistent with the other prompts in this command. Prefer Fprint so the user can type on the same line and stdout matches expected prompt formatting.

Copilot uses AI. Check for mistakes.
Comment thread pkg/tscli/client.go
Comment on lines +104 to +137
type oauthBearerTransport struct {
rt http.RoundTripper
clientID string
clientSecret string
tokenURL string

mu sync.Mutex
accessToken string
}

func (t *oauthBearerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
accessToken, err := t.token(req.Context())
if err != nil {
return nil, err
}
clone := req.Clone(req.Context())
clone.Header = req.Header.Clone()
clone.Header.Set("Authorization", "Bearer "+accessToken)
return t.rt.RoundTrip(clone)
}

func (t *oauthBearerTransport) token(ctx context.Context) (string, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.accessToken != "" {
return t.accessToken, nil
}
resp, err := oauth.ExchangeClientCredentialsAtURL(ctx, t.clientID, t.clientSecret, t.tokenURL)
if err != nil {
return "", fmt.Errorf("exchange oauth credentials: %w", err)
}
t.accessToken = resp.AccessToken
return t.accessToken, nil
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oauthBearerTransport caches the access token forever (t.accessToken), but the token response includes expiry. After expiration, all API calls will start failing until the process restarts. Track the token expiry (e.g., store expiresAt/expiresIn) and refresh when expired (or on 401) to keep long-running commands/retries reliable.

Copilot uses AI. Check for mistakes.
Comment thread cmd/tscli/config/setup/cli.go
Comment on lines +17 to +23
```
-h, --help help for setup
--private-key string AGE private key stored in config when --private-key-source=config
--private-key-command string Command that returns the AGE private key when --private-key-source=command
--private-key-source string How to provide the AGE private key: config, env, or command
--public-key string AGE public key used to encrypt persisted secrets
```
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generated command doc doesn’t match the actual config encryption setup command implementation: it documents --private-key and --private-key-source=config but the command uses --private-key-path and private-key-source=path (and doesn’t define --private-key). Regenerate the docs (or adjust the command flags) so help text and docs stay consistent.

Copilot uses AI. Check for mistakes.
Comment thread pkg/tscli/client.go
Comment on lines 44 to 49
if tailnet == "" {
return nil, fmt.Errorf("tailnet is required")
}
if apiKey == "" {
if apiKey == "" && (oauthClientID == "" || oauthClientSecret == "") {
return nil, fmt.Errorf("api-key is required")
}
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In New(), the credential validation now allows OAuth, but the returned error still says "api-key is required". This will be misleading when users forget to supply OAuth credentials. Consider updating the message to reflect that either an API key or an OAuth client id/secret pair is required (or include which pieces are missing).

Copilot uses AI. Check for mistakes.
Comment thread cmd/tscli/config/setup/cli.go
Comment thread cmd/tscli/config/setup/cli.go Outdated
@jaxxstorm jaxxstorm merged commit a8af715 into main Apr 15, 2026
1 check passed
@jaxxstorm jaxxstorm deleted the oauth_encryption branch April 15, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants