Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@cevian
Copy link
Contributor

@cevian cevian commented Oct 15, 2025

Summary

  • Adds comprehensive specification for tiger db create role command
  • Documents read-only role creation for safe AI agent database access
  • Defines command structure, options, and behavior

Key Features Documented

  • Role creation: Optional positional service ID, required --name flag
  • Read-only enforcement: Permanent role-based read-only via tsdb_admin.read_only_role
  • Grant inheritance: --from flag accepts comma-separated list of roles
  • Performance safety: --statement-timeout for query limits
  • Password options: Auto-generate, explicit value, TIGER_NEW_PASSWORD env var, or interactive prompt

Design Decisions

  • --name flag is required (not positional) to prevent accidental role creation with service IDs
  • Service ID is optional positional parameter (uses default service if omitted)
  • Password auto-generation is default behavior for better security
  • Read-only mode uses role-level enforcement that cannot be bypassed

Documentation Includes

  • Multiple usage examples covering common scenarios
  • Detailed explanation of read-only mode implementation
  • Use cases for AI agents and automated tools
  • Safety patterns combining read-only + statement timeouts
  • Password behavior precedence (flag > env var > auto-generate)

🤖 Generated with Claude Code

Copy link
Collaborator

@nathanjcochran nathanjcochran left a comment

Choose a reason for hiding this comment

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

LGTM to me overall 👍.

Left a couple comments/questions below, but nothing major.

One other thought: I know CREATE ROLE accepts a bunch of options (see the docs). Do we need to support configuring any of those via the CLI? Even if not, we should probably document the exact options that are set when creating roles via this command.

**Combining with `--from` for Safe Table Access:**
The `--from` flag allows you to create a read-only role that inherits the same table access as your application role:
Copy link
Collaborator

@nathanjcochran nathanjcochran Oct 15, 2025

Choose a reason for hiding this comment

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

What is the default behavior if --from is not specified? What permissions does the new role have in that case? We should probably document the exact behavior (it's not immediately clear to me, as someone who hasn't worked a ton with Postgres roles)

@cevian cevian force-pushed the add-db-create-role-spec branch from 09b10d2 to 46d46b9 Compare October 21, 2025 09:48
@cevian cevian changed the title Add spec for tiger db create role command [do not merge: waiting on tsdbadmin fix] Add spec for tiger db create role command Oct 21, 2025
cevian and others added 5 commits October 22, 2025 15:32
Adds comprehensive specification for creating database roles with read-only enforcement for safe AI agent access.

Key features:
- Role creation with optional service ID (uses default if omitted)
- `--read-only` flag for permanent read-only enforcement via tsdb_admin extension
- `--from` flag to inherit grants from comma-separated list of roles
- `--statement-timeout` for performance safety
- Auto-generated passwords with manual override options
- `--name` flag required to prevent accidental role creation

Includes detailed documentation on:
- Read-only mode implementation and use cases
- Password behavior (auto-generate, explicit value, env var, prompt)
- Design rationale for requiring explicit --name flag
- AI agent safety patterns combining read-only + timeouts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit implements the core functionality of the `tiger db create role`
command as specified in specs/spec.md. The command creates PostgreSQL roles
with optional read-only enforcement, statement timeouts, and role inheritance.

## What Works

✅ Basic role creation with auto-generated passwords
✅ Role creation with explicit passwords (--password flag or TIGER_NEW_PASSWORD env var)
✅ Read-only enforcement via --read-only flag (tsdb_admin.read_only_role GUC)
✅ Statement timeout configuration via --statement-timeout flag
✅ Password storage in configured storage (keyring/pgpass/none)
✅ Output formatting (JSON/YAML/table) via --output flag
✅ SQL injection prevention for role names using pgx.Identifier.Sanitize()
✅ Password security: fails if password contains single quotes (no escaping needed)
✅ Comprehensive unit tests for SQL generation functions
✅ Integration tests for basic role creation scenarios

## Known Issue: --from Flag with tsdbadmin

❌ The --from flag FAILS when trying to inherit from tsdbadmin role
❌ Error: "permission denied to grant role \"tsdbadmin\" (SQLSTATE 42501)"

### Root Cause

PostgreSQL's CREATE ROLE ... IN ROLE syntax is equivalent to GRANT:
```sql
CREATE ROLE new_role IN ROLE tsdbadmin  -- equivalent to:
GRANT tsdbadmin TO new_role
```

To grant a role, you need either:
1. Be a superuser (tsdbadmin is NOT a superuser in TimescaleDB Cloud)
2. Have the role WITH ADMIN OPTION (tsdbadmin does NOT have ADMIN OPTION on itself)

Since tsdbadmin doesn't have ADMIN OPTION on itself, it cannot grant itself
to other roles, even though it can create new roles.

### Impact

This is a CRITICAL limitation because the most common use case is:
```bash
tiger db create role --name ai_analyst --from tsdbadmin --read-only
```

Users expect this to create a role with the same permissions as tsdbadmin,
which is essential for AI agents, analytics users, etc.

### Solution Options (TODO)

1. **Copy grants explicitly** (recommended): When --from is specified, query
   all grants that the source role has and apply them directly to the new role
   instead of using IN ROLE. This bypasses the ADMIN OPTION requirement.

2. **Try IN ROLE, fallback to copying**: Try the simple IN ROLE approach first,
   if it fails with permission denied, automatically fall back to copying grants.

3. **Request platform change**: Ask TimescaleDB Cloud to grant tsdbadmin the
   ADMIN OPTION on itself so it can grant itself to other roles.

4. **Document limitation**: Update docs to explain that --from only works with
   roles you've created yourself, not with tsdbadmin.

### Integration Test Status

Most integration tests PASS:
- ✅ CreateRole_Basic
- ✅ CreateRole_WithExplicitPassword
- ❌ CreateRole_WithInheritedGrants (fails due to --from tsdbadmin)
- ✅ CreateRole_ReadOnly
- ✅ CreateRole_WithStatementTimeout
- ❌ CreateRole_AllOptions (fails due to --from tsdbadmin)
- ✅ CreateRole_DuplicateName

Tests that use --from tsdbadmin fail but are left in place to demonstrate
the issue that needs to be fixed.

## Technical Details

### Password Handling
- Passwords are quoted as PostgreSQL string literals: 'password'
- Single quotes in passwords cause an error (fail-fast approach)
- PostgreSQL's CREATE ROLE doesn't support parameterized queries for passwords
- Auto-generated passwords use base64 URL-safe encoding (no special chars)

### SQL Generation
- Role names: pgx.Identifier.Sanitize() for proper quoting and escaping
- Passwords: Manual quoting with single quote validation
- Multiple roles in --from: Comma-separated quoted identifiers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…g for --from=tsdbadmin tests

## New Test: CreateRole_ReadOnlyWithInheritance

This test verifies that the --from flag works correctly when inheriting from
a role we control (not tsdbadmin), and that read-only enforcement actually works:

1. Creates a base role (tsdbadmin automatically gets ADMIN OPTION on it)
2. Grants CREATE privilege on public schema to base role
3. Base role creates a table and inserts test data
4. Creates a read-only role with --from base_role --read-only
5. Verifies the read-only role CAN read the data
6. Verifies the read-only role CANNOT write (enforced by tsdb_admin.read_only_role)
7. Cleans up the test table

This demonstrates that:
- The --from flag works when we have ADMIN OPTION on the source role
- Read-only enforcement prevents writes while allowing reads
- Role inheritance allows the new role to access tables created by the base role

## Skip Flag for --from=tsdbadmin Tests

Added `skipFromTsdbadminTests` constant (default: true) to control whether to
skip tests that use --from=tsdbadmin. When true, these tests are skipped with
a warning explaining the limitation:

⚠️  Skipping --from=tsdbadmin test: tsdbadmin doesn't have ADMIN OPTION on
itself, so it can't grant itself to other roles. This is a known limitation
that needs to be fixed.

Affected tests:
- CreateRole_WithInheritedGrants
- CreateRole_AllOptions

Set `skipFromTsdbadminTests = false` to see the actual permission failures
when debugging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
## Problem Solved
The --from tsdbadmin flag was failing with "permission denied to grant role"
because tsdbadmin doesn't have ADMIN OPTION on itself, preventing it from
granting itself to other roles via standard PostgreSQL IN ROLE syntax.

## Solution
Use TimescaleDB Cloud special functions when --from includes tsdbadmin:
- timescale_functions.create_bare_readonly_role($1, $2) to create the role
- timescale_functions.grant_tsdbadmin_to_role($1) to grant privileges

## Implementation Details

### Requirements When Using --from tsdbadmin
1. **Must use --read-only flag**: The create_bare_readonly_role function only
   creates read-only roles, so --read-only is required
2. **Cannot use --statement-timeout**: Permission denied to ALTER roles created
   with special functions, so this combination is now disallowed

### Code Changes
- Updated createRoleWithOptions() to detect tsdbadmin in --from list
- Use special functions path when tsdbadmin is present
- Enforce --read-only requirement with clear error message
- Disallow --statement-timeout with clear error message
- Use parameterized queries ($1, $2) for function calls

### Test Updates
- Removed skipFromTsdbadminTests flag (no longer needed)
- Updated CreateRole_WithInheritedGrants to use --read-only
- Updated CreateRole_AllOptions to remove --statement-timeout
- Added CreateRole_ReadOnlyWithInheritance test for non-tsdbadmin inheritance
- All CreateRole integration tests now pass

### Added Integration Test Script
Created scripts/test-integration.sh for easier test execution:
- Loads environment from .env automatically
- Builds binary before testing
- Supports pattern matching for specific tests
- Provides colored output for better readability
- Documented in CLAUDE.md

## Test Results
✅ All ServiceLifecycleIntegration tests passing (66s)
✅ CreateRole with --from tsdbadmin now works correctly
✅ Read-only enforcement verified
✅ Role inheritance verified

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…oject-id flag

## Problem
TestAuthenticationErrorsIntegration was failing because it was using
--project-id flag on service and db commands, but this flag doesn't exist.
The test was getting "unknown flag: --project-id" errors instead of
authentication errors.

## Root Cause
The --project-id flag doesn't exist as a persistent flag in the CLI.
It's only valid for the auth login command where it specifies which
project to authenticate against.

The available persistent flags are:
- --config-dir
- --debug
- --service-id
- --analytics
- --password-storage
- --skip-update-check
- --color

## Fix
Removed --project-id flag from all service and db command test cases
in TestAuthenticationErrorsIntegration. The flag remains in auth login
commands where it's valid.

## Test Results
✅ All integration tests now pass (67.4s)
- TestServiceLifecycleIntegration: PASS
- TestAuthenticationErrorsIntegration: PASS
- TestInstallMCPForEditor_Integration: PASS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@cevian cevian force-pushed the add-db-create-role-spec branch from 46d46b9 to bc50420 Compare October 22, 2025 13:36
@cevian cevian changed the title [do not merge: waiting on tsdbadmin fix] Add spec for tiger db create role command Add spec for tiger db create role command Oct 22, 2025
{
name: "db connect",
args: []string{"db", "connect", "non-existent-service", "--project-id", projectID},
args: []string{"db", "connect", "non-existent-service"},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixes like this are for things that were broken from before

Copy link
Collaborator

@nathanjcochran nathanjcochran left a comment

Choose a reason for hiding this comment

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

Left a handful of comments, but LGTM overall.

Didn't test yet, but I can do that in a bit if you'd like.

Based on reviewer feedback from nathanjcochran:

Code improvements:
- Fix duplicative GRANT logic: moved otherRoles grants inside hasTsdbadmin block
  since IN ROLE clause handles grants in the standard path
- Switch --from flag to StringSliceVar for consistency with --addons flag pattern
  (supports both --from role1,role2 and --from role1 --from role2)
- Remove unnecessary cleanupRoles() function - service deletion handles cleanup
- Rename TestServiceNotFound to TestServiceNotFoundIntegration to match pattern
- Make error message checks in tests more flexible for API variations

Documentation updates:
- Change TIGER_NEW_PASSWORD example to inline format (not export)
- Replace "PostgreSQL GUCs" with clearer "PostgreSQL Configuration Parameters"
- Document default behavior when --from is not specified (LOGIN/PASSWORD only)
- Update CLAUDE.md for simplified test script usage

Test script simplification:
- Reduce test-integration.sh from 84 to 31 lines following Unix philosophy
- Use simple env + go test approach with $@ for argument passthrough
- Keep essential .env checks and build step

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@cevian cevian merged commit fd9f8a3 into main Oct 23, 2025
2 checks passed
@cevian cevian deleted the add-db-create-role-spec branch October 23, 2025 11:14
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.

3 participants