Merged in feature/permit.api.keys (pull request #196)

permit keys

* permit keys

use the environment from the api key if requested
This commit is contained in:
Jay Brown
2025-12-11 22:18:42 +00:00
parent 8c218f162b
commit ab7072ac90
6 changed files with 377 additions and 89 deletions
@@ -109,6 +109,47 @@ func main() {
}
}
// resolveAPIKeyScope resolves the project and environment IDs from the API key scope
// when the user specifies -project use-key. It updates the config with the resolved values.
//
// Parameters:
// - config: Pointer to AppConfig that will be updated with resolved project/environment IDs
//
// Returns:
// - error: Error if scope resolution fails or required values cannot be determined
func resolveAPIKeyScope(config *AppConfig) error {
if config.PermitAPIKey == "" {
return fmt.Errorf("PERMIT_KEY environment variable is required when using -project use-key")
}
fmt.Println("Detecting project and environment from API key scope...")
httpClient := &http.Client{Timeout: 30 * time.Second}
scope, err := getAPIKeyScope(httpClient, *config)
if err != nil {
return fmt.Errorf("failed to get API key scope: %w", err)
}
if scope.ProjectID == "" {
return fmt.Errorf("API key does not have a project scope - use an org-level key with explicit -project flag, or use a project/environment-scoped key")
}
config.PermitProjectID = scope.ProjectID
fmt.Printf(" Detected project: %s\n", scope.ProjectID)
if scope.EnvironmentID != "" {
config.PermitEnvID = scope.EnvironmentID
fmt.Printf(" Detected environment: %s\n", scope.EnvironmentID)
} else if config.PermitEnvID == "" {
return fmt.Errorf("API key is project-scoped but -env flag not provided; specify environment or use an environment-scoped key")
} else {
fmt.Printf(" Using provided environment: %s\n", config.PermitEnvID)
}
fmt.Println()
return nil
}
func run() error {
config := parseFlags()
@@ -126,9 +167,7 @@ func run() error {
fmt.Println(strings.Repeat("=", 60))
}
sanityCheckCredentials()
// Check if we should list projects/environments
// Check if we should list projects/environments (Permit-only operation)
if config.PermitProjectID == "list" {
if config.PermitAPIKey == "" {
return fmt.Errorf("PERMIT_KEY environment variable is not set")
@@ -137,6 +176,16 @@ func run() error {
return nil
}
// Resolve API key scope if using project-level or environment-level key
if config.PermitProjectID == "use-key" {
if err := resolveAPIKeyScope(&config); err != nil {
return err
}
}
// Run sanity check for credentials (AWS + Permit)
sanityCheckCredentials(config)
// Validate configuration
if err := validateConfig(config); err != nil {
return fmt.Errorf("configuration error: %w", err)
@@ -197,57 +246,8 @@ func run() error {
}
fmt.Println()
if config.DeleteUsers {
// Delete mode
fmt.Printf("Found %d users to DELETE\n", len(users))
fmt.Printf("Will delete users from Permit.io project: %s\n", config.PermitProjectID)
fmt.Printf("Then delete from Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Println(strings.Repeat("=", 60))
if !config.DryRun {
fmt.Println("⚠️ WARNING: This will permanently delete users!")
}
fmt.Println(strings.Repeat("=", 60))
// Process user deletions
result := deleteUsers(ctx, config, cognitoClient, users, auditLogger)
// Print delete summary
printDeleteSummary(result)
} else if config.DisableUsers {
// Disable mode
fmt.Printf("Found %d users to DISABLE\n", len(users))
fmt.Printf("Will disable users in Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Println(strings.Repeat("=", 60))
// Process user disabling
result := disableEnableUsers(ctx, config, cognitoClient, users, auditLogger, "disable")
// Print disable/enable summary
printDisableEnableSummary(result)
} else if config.EnableUsers {
// Enable mode
fmt.Printf("Found %d users to ENABLE\n", len(users))
fmt.Printf("Will enable users in Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Println(strings.Repeat("=", 60))
// Process user enabling
result := disableEnableUsers(ctx, config, cognitoClient, users, auditLogger, "enable")
// Print disable/enable summary
printDisableEnableSummary(result)
} else {
// Create mode (existing functionality)
fmt.Printf("Found %d users to import\n", len(users))
fmt.Printf("Will create users in Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Printf("Then sync to Permit.io project: %s\n", config.PermitProjectID)
fmt.Println(strings.Repeat("=", 60))
// Process users
result := processUsers(ctx, config, cognitoClient, users, auditLogger)
// Print summary
printSummary(result)
}
// Execute the requested operation
executeOperation(ctx, config, cognitoClient, users, auditLogger)
// Log dry run end if applicable
if config.DryRun {
@@ -260,12 +260,70 @@ func run() error {
return nil
}
// executeOperation runs the appropriate user operation based on the config flags.
// It handles delete, disable, enable, and create/update modes.
//
// Parameters:
// - ctx: Context for the operation
// - config: Application configuration specifying the operation mode
// - cognitoClient: AWS Cognito client for user operations
// - users: Slice of user data from the CSV file
// - auditLogger: Logger for audit trail
func executeOperation(ctx context.Context, config AppConfig, cognitoClient *cognitoidentityprovider.Client, users []map[string]string, auditLogger *AuditLogger) {
if config.DeleteUsers {
executeDeleteOperation(ctx, config, cognitoClient, users, auditLogger)
} else if config.DisableUsers {
executeDisableEnableOperation(ctx, config, cognitoClient, users, auditLogger, "disable")
} else if config.EnableUsers {
executeDisableEnableOperation(ctx, config, cognitoClient, users, auditLogger, "enable")
} else {
executeCreateOperation(ctx, config, cognitoClient, users, auditLogger)
}
}
// executeDeleteOperation handles the delete users workflow
func executeDeleteOperation(ctx context.Context, config AppConfig, cognitoClient *cognitoidentityprovider.Client, users []map[string]string, auditLogger *AuditLogger) {
fmt.Printf("Found %d users to DELETE\n", len(users))
fmt.Printf("Will delete users from Permit.io project: %s\n", config.PermitProjectID)
fmt.Printf("Then delete from Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Println(strings.Repeat("=", 60))
if !config.DryRun {
fmt.Println("⚠️ WARNING: This will permanently delete users!")
}
fmt.Println(strings.Repeat("=", 60))
result := deleteUsers(ctx, config, cognitoClient, users, auditLogger)
printDeleteSummary(result)
}
// executeDisableEnableOperation handles the disable/enable users workflow
func executeDisableEnableOperation(ctx context.Context, config AppConfig, cognitoClient *cognitoidentityprovider.Client, users []map[string]string, auditLogger *AuditLogger, operation string) {
opUpper := strings.ToUpper(operation)
fmt.Printf("Found %d users to %s\n", len(users), opUpper)
fmt.Printf("Will %s users in Cognito User Pool: %s\n", operation, config.CognitoUserPoolID)
fmt.Println(strings.Repeat("=", 60))
result := disableEnableUsers(ctx, config, cognitoClient, users, auditLogger, operation)
printDisableEnableSummary(result)
}
// executeCreateOperation handles the create/update users workflow
func executeCreateOperation(ctx context.Context, config AppConfig, cognitoClient *cognitoidentityprovider.Client, users []map[string]string, auditLogger *AuditLogger) {
fmt.Printf("Found %d users to import\n", len(users))
fmt.Printf("Will create users in Cognito User Pool: %s\n", config.CognitoUserPoolID)
fmt.Printf("Then sync to Permit.io project: %s\n", config.PermitProjectID)
fmt.Println(strings.Repeat("=", 60))
result := processUsers(ctx, config, cognitoClient, users, auditLogger)
printSummary(result)
}
func parseFlags() AppConfig {
var config AppConfig
// Permit.io flags
flag.StringVar(&config.PermitProjectID, "project", "", "Permit.io project ID or key (required)")
flag.StringVar(&config.PermitEnvID, "env", "", "Permit.io environment ID or key (required)")
flag.StringVar(&config.PermitProjectID, "project", "", "Permit.io project ID/key, 'list' to show available, or 'use-key' to auto-detect from API key scope")
flag.StringVar(&config.PermitEnvID, "env", "", "Permit.io environment ID or key (required unless using 'use-key' with env-scoped API key)")
flag.StringVar(&config.PermitBaseURL, "api-url", "https://api.permit.io", "Permit.io API base URL")
// File flag
@@ -309,6 +367,9 @@ func parseFlags() AppConfig {
fmt.Fprintf(os.Stderr, " %s -project myproject -env production -csv users.csv --dry-run\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\nTo list available projects and environments:\n")
fmt.Fprintf(os.Stderr, " %s -project list\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\nTo use a project/environment-scoped API key (auto-detect project/env):\n")
fmt.Fprintf(os.Stderr, " %s -project use-key -csv users.csv\n", os.Args[0])
fmt.Fprintf(os.Stderr, " (For project-scoped keys, also specify -env)\n")
}
flag.Parse()
+16 -6
View File
@@ -40,7 +40,13 @@ type CognitoUserResponse struct {
Enabled bool
}
func sanityCheckCredentials() {
// sanityCheckCredentials validates that both AWS and Permit.io credentials are properly configured.
// It checks AWS credentials by calling STS GetCallerIdentity and Permit.io credentials by
// calling the API key scope endpoint.
//
// Parameters:
// - appConfig: Application configuration containing Permit.io API key and base URL
func sanityCheckCredentials(appConfig AppConfig) {
awsProfile := os.Getenv("AWS_PROFILE")
var cfgOpts []func(*config.LoadOptions) error
@@ -62,18 +68,22 @@ func sanityCheckCredentials() {
os.Exit(1)
}
permitKey := os.Getenv("PERMIT_KEY")
if permitKey == "" {
if appConfig.PermitAPIKey == "" {
fmt.Fprintf(os.Stderr, "Permit.io: PERMIT_KEY environment variable is not set\n")
os.Exit(1)
}
req, _ := http.NewRequest("GET", "https://api.permit.io/v2/projects", nil)
req.Header.Add("Authorization", "Bearer "+permitKey)
// Use the API key scope endpoint to validate the key - this works for all key types
req, _ := http.NewRequest("GET", appConfig.PermitBaseURL+"/v2/api-key/scope", nil)
req.Header.Add("Authorization", "Bearer "+appConfig.PermitAPIKey)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {
fmt.Fprintf(os.Stderr, "Permit.io: Credential check failed: %v, status=%v\n", err, resp.StatusCode)
statusCode := 0
if resp != nil {
statusCode = resp.StatusCode
}
fmt.Fprintf(os.Stderr, "Permit.io: Credential check failed: %v, status=%v\n", err, statusCode)
os.Exit(1)
}
resp.Body.Close()
+84
View File
@@ -0,0 +1,84 @@
#!/bin/bash
# get.scope.from.key.sh - Retrieves the scope (project/environment) for a Permit.io API key
#
# Usage: ./get.scope.from.key.sh <PERMIT_API_KEY>
#
# This script calls the Permit.io API to introspect the provided API key and
# returns the organization, project, and environment IDs and names associated with it.
if [ -z "$1" ]; then
echo "Usage: $0 <PERMIT_API_KEY>" >&2
echo "Example: $0 permit_key_abc123..." >&2
exit 1
fi
API_KEY="$1"
BASE_URL="https://api.permit.io"
# Get the scope IDs
SCOPE=$(curl -s -H "Authorization: Bearer ${API_KEY}" "${BASE_URL}/v2/api-key/scope")
if [ -z "$SCOPE" ] || echo "$SCOPE" | jq -e '.error' > /dev/null 2>&1; then
echo "Error fetching API key scope:"
echo "$SCOPE" | jq
exit 1
fi
ORG_ID=$(echo "$SCOPE" | jq -r '.organization_id // empty')
PROJECT_ID=$(echo "$SCOPE" | jq -r '.project_id // empty')
ENV_ID=$(echo "$SCOPE" | jq -r '.environment_id // empty')
echo "API Key Scope"
echo "============="
echo ""
# Organization info
if [ -n "$ORG_ID" ]; then
echo "Organization:"
echo " ID: $ORG_ID"
fi
# Project info - fetch name if we have a project ID
if [ -n "$PROJECT_ID" ]; then
PROJECT_INFO=$(curl -s -H "Authorization: Bearer ${API_KEY}" "${BASE_URL}/v2/projects/${PROJECT_ID}" 2>/dev/null)
PROJECT_NAME=$(echo "$PROJECT_INFO" | jq -r '.name // empty' 2>/dev/null)
PROJECT_KEY=$(echo "$PROJECT_INFO" | jq -r '.key // empty' 2>/dev/null)
echo ""
echo "Project:"
echo " ID: $PROJECT_ID"
if [ -n "$PROJECT_NAME" ]; then
echo " Name: $PROJECT_NAME"
fi
if [ -n "$PROJECT_KEY" ]; then
echo " Key: $PROJECT_KEY"
fi
fi
# Environment info - fetch name if we have an environment ID
if [ -n "$ENV_ID" ] && [ -n "$PROJECT_ID" ]; then
ENV_INFO=$(curl -s -H "Authorization: Bearer ${API_KEY}" "${BASE_URL}/v2/projects/${PROJECT_ID}/envs/${ENV_ID}" 2>/dev/null)
ENV_NAME=$(echo "$ENV_INFO" | jq -r '.name // empty' 2>/dev/null)
ENV_KEY=$(echo "$ENV_INFO" | jq -r '.key // empty' 2>/dev/null)
echo ""
echo "Environment:"
echo " ID: $ENV_ID"
if [ -n "$ENV_NAME" ]; then
echo " Name: $ENV_NAME"
fi
if [ -n "$ENV_KEY" ]; then
echo " Key: $ENV_KEY"
fi
fi
# Determine key type
echo ""
echo "---"
if [ -n "$ENV_ID" ]; then
echo "Key Type: Environment-scoped (can use: -project use-key)"
elif [ -n "$PROJECT_ID" ]; then
echo "Key Type: Project-scoped (can use: -project use-key -env <env_key>)"
else
echo "Key Type: Organization-scoped (must use: -project <project_key> -env <env_key>)"
fi
@@ -30,6 +30,58 @@ type RoleAssignment struct {
Tenant string `json:"tenant"`
}
// APIKeyScope represents the scope information returned by the Permit.io API key introspection endpoint.
// It contains the organization, project, and environment IDs that the API key has access to.
// Organization-level keys will have only OrganizationID set.
// Project-level keys will have OrganizationID and ProjectID set.
// Environment-level keys will have all three fields set.
type APIKeyScope struct {
OrganizationID string `json:"organization_id"`
ProjectID string `json:"project_id,omitempty"`
EnvironmentID string `json:"environment_id,omitempty"`
}
// getAPIKeyScope retrieves the scope information for the provided Permit.io API key.
// This allows the tool to introspect a project-level or environment-level API key
// to automatically determine the project and environment IDs without requiring
// the user to specify them explicitly.
//
// Parameters:
// - client: HTTP client for making API requests
// - config: Application configuration containing the Permit.io API key and base URL
//
// Returns:
// - *APIKeyScope: The scope information containing organization, project, and environment IDs
// - error: Error if the API call fails or response cannot be parsed
func getAPIKeyScope(client *http.Client, config AppConfig) (*APIKeyScope, error) {
url := fmt.Sprintf("%s/v2/api-key/scope", config.PermitBaseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+config.PermitAPIKey)
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var scope APIKeyScope
if err := json.NewDecoder(resp.Body).Decode(&scope); err != nil {
return nil, fmt.Errorf("error decoding response: %w", err)
}
return &scope, nil
}
func createPermitUser(client *http.Client, config AppConfig, cognitoUser *CognitoUserResponse) error {
url := fmt.Sprintf("%s/v2/facts/%s/%s/users", config.PermitBaseURL, config.PermitProjectID, config.PermitEnvID)
+103 -22
View File
@@ -5,6 +5,7 @@ A CLI tool that creates, updates, deletes, disables, and enables users in AWS Co
## Overview
This tool:
1. Reads user data from a CSV file (supports comments with #)
2. **Idempotently** creates or updates users in AWS Cognito
3. **Idempotently** creates or updates corresponding users in Permit.io
@@ -28,6 +29,7 @@ This tool:
### Delete Mode (`--delete-users`)
Runs the inverse of the normal create operation, deleting any users listed in the CSV:
1. Reads user data from a CSV file
2. Finds each user in Permit.io by email (skips if not found)
3. Deletes the user from Permit.io (if exists)
@@ -45,6 +47,7 @@ email,first_name,last_name,admin,viewer
### Disable Mode (`--disable-users`)
Disables users in AWS Cognito without deleting them. This prevents users from logging in while preserving their account and settings:
1. Reads user data from a CSV file
2. Checks if each user exists in Cognito
3. Disables the user account in Cognito (if exists and enabled)
@@ -52,6 +55,7 @@ Disables users in AWS Cognito without deleting them. This prevents users from lo
5. **Idempotent**: Reports "already disabled" for users who are already disabled
**Important Notes**:
- Disabled users cannot authenticate or access applications
- User data, roles, and settings are preserved
- Users can be re-enabled later using `--enable-users`
@@ -61,6 +65,7 @@ Disables users in AWS Cognito without deleting them. This prevents users from lo
### Enable Mode (`--enable-users`)
Re-enables previously disabled users in AWS Cognito:
1. Reads user data from a CSV file
2. Checks if each user exists in Cognito
3. Enables the user account in Cognito (if exists and disabled)
@@ -68,6 +73,7 @@ Re-enables previously disabled users in AWS Cognito:
5. **Idempotent**: Reports "already enabled" for users who are already enabled
**Important Notes**:
- Only works on existing Cognito users
- Does not create new users - use default mode for user creation
- Only affects Cognito enable/disable status - does not modify roles
@@ -76,16 +82,32 @@ Re-enables previously disabled users in AWS Cognito:
## Prerequisites
1. **AWS Account** with:
- An existing Cognito User Pool
- Appropriate IAM permissions for Cognito operations
- AWS credentials configured
If you are running this from the devbox environment you can use the `task aws:login` to perform the login for you and authenticate your cli to be the same as your broswer aws credentials.
If you are running this in the aws cloud console it should just work.
2. **Permit.io Account** with:
- A project and environment set up
- API key with user creation permissions
- API key with user creation permissions (organization, project, or environment-level key)
## Configuration
### Permit.io API Key Types
Permit.io supports three levels of API keys, and this tool works with all of them:
| Key Type | Scope | Usage |
| ---------------- | ------------------------------------ | --------------------------------------------------------------- |
| **Organization** | All projects and environments | Use with explicit `-project` and `-env` flags |
| **Project** | Single project, all its environments | Use with `-project use-key` and explicit `-env` flag |
| **Environment** | Single environment only | Use with `-project use-key` (auto-detects both project and env) |
**Recommendation**: Use environment-scoped keys for production deployments as they provide the principle of least privilege.
### Environment Variables
Create a `.env` file (see `.env.example`):
@@ -161,10 +183,36 @@ export $(cat .env | xargs)
./cognito-permit-sync -project list
```
### Using Project/Environment-Scoped API Keys
If you have a **project-scoped** or **environment-scoped** Permit.io API key (rather than an organization-level key), you can use `-project use-key` to automatically detect the project and environment from the API key:
```bash
# With an environment-scoped key (auto-detects both project and environment)
./cognito-permit-sync -project use-key -csv users.csv
# With a project-scoped key (auto-detects project, requires -env flag)
./cognito-permit-sync -project use-key -env production -csv users.csv
```
This is useful when:
- You have a scoped API key and don't know the project/environment IDs
- You want to avoid hardcoding project IDs in scripts
- You're working with environment-specific deployments
To check what project/environment your API key is scoped to:
```bash
./get.scope.from.key.sh your_permit_api_key
```
### Command Line Options
- `-project`: Permit.io project ID or key (required, or "list" to show available projects)
- `-env`: Permit.io environment ID or key (required)
- `-project`: Permit.io project ID/key, or special values:
- `list` - Show available projects and environments
- `use-key` - Auto-detect project from API key scope
- `-env`: Permit.io environment ID or key (required unless using `use-key` with an environment-scoped API key)
- `-csv`: Path to CSV file containing users (required)
- `-api-url`: Permit.io API base URL (default: https://api.permit.io)
- `--delete-users`: Delete users instead of creating/updating them
@@ -181,10 +229,12 @@ export $(cat .env | xargs)
For each user in the CSV:
1. **Checks if user exists in Cognito**:
- If exists: Retrieves existing user details
- If exists: Retrieves existing user details (including Subject ID)
- If not exists: Creates new user with email as username, sets email_verified to true
2. **Checks if user exists in Permit.io**:
- If exists: Uses existing user
- If not exists: Creates user with Cognito Subject ID as key
@@ -194,11 +244,26 @@ For each user in the CSV:
- Adds new roles specified in CSV
- Skips roles that are already assigned correctly
#### Syncing Existing Cognito Users to a New Permit.io Project
The tool handles the scenario where users already exist in Cognito but need to be synced to a different Permit.io project:
- **Cognito**: User is detected as existing, details are retrieved (no changes made)
- **Permit.io**: User is created in the target project using their existing Cognito Subject ID
- **Roles**: Assigned according to the CSV specification
This is useful when:
- Migrating users to a new Permit.io project
- Setting up a new environment with existing Cognito users
- Syncing users across multiple Permit.io projects
### Delete Mode (`--delete-users`)
For each user in the CSV:
1. **Finds user in Permit.io by email**:
- If found: Proceeds with deletion
- If not found: Skips Permit.io deletion
@@ -213,10 +278,12 @@ For each user in the CSV:
For each user in the CSV:
1. **Checks if user exists in Cognito**:
- If not found: Reports error and skips
- If found: Proceeds with disable check
2. **Checks current user status**:
- If already disabled: Reports "already disabled" and skips
- If enabled: Proceeds with disable operation
@@ -230,10 +297,12 @@ For each user in the CSV:
For each user in the CSV:
1. **Checks if user exists in Cognito**:
- If not found: Reports error and skips
- If found: Proceeds with enable check
2. **Checks current user status**:
- If already enabled: Reports "already enabled" and skips
- If disabled: Proceeds with enable operation
@@ -245,6 +314,7 @@ For each user in the CSV:
### Dry-Run Mode (`--dry-run`)
Works with all operation modes:
- Simulates all operations without making actual API calls
- Shows exactly what would be done
- Logs all actions to `dry-run-audit.log`
@@ -261,6 +331,7 @@ Works with all operation modes:
### Audit Log Format
Each audit entry includes:
- Timestamp (YYYY-MM-DD HH:MM:SS)
- Action type (CREATE, DELETE, DISABLE, ENABLE, ASSIGN_ROLE, UNASSIGN_ROLE, SKIP)
- System (COGNITO, PERMIT)
@@ -353,6 +424,7 @@ The tool handles various error scenarios gracefully:
### Role Management Examples
If a user currently has roles `[admin, viewer, old_role]` and CSV specifies `[admin, editor]`:
- ✅ Keeps: `admin` (already assigned)
- Adds: `editor` (new role)
- Removes: `viewer`, `old_role` (no longer needed)
@@ -363,24 +435,25 @@ Your AWS credentials need these Cognito permissions:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminDeleteUser",
"cognito-idp:AdminDisableUser",
"cognito-idp:AdminEnableUser"
],
"Resource": "arn:aws:cognito-idp:REGION:ACCOUNT:userpool/USER_POOL_ID"
}
]
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminDeleteUser",
"cognito-idp:AdminDisableUser",
"cognito-idp:AdminEnableUser"
],
"Resource": "arn:aws:cognito-idp:REGION:ACCOUNT:userpool/USER_POOL_ID"
}
]
}
```
**New Permissions Required**:
- `cognito-idp:AdminDisableUser`: Required for `--disable-users` functionality
- `cognito-idp:AdminEnableUser`: Required for `--enable-users` functionality
@@ -401,10 +474,12 @@ Your AWS credentials need these Cognito permissions:
## Troubleshooting
### "User already exists" (No Longer an Error)
-**Fixed**: Tool now handles existing users gracefully
-**Action**: Continues with role management for existing users
### Audit Log Analysis
```bash
# Check recent audit activity
tail -f audit.log
@@ -423,17 +498,20 @@ cat dry-run-audit.log
```
### Disable/Enable Issues
- **Permission Errors**: Ensure your AWS credentials have `AdminDisableUser` and `AdminEnableUser` permissions
- **User Not Found**: Disable/enable operations only work on existing Cognito users
- **Already Disabled/Enabled**: The tool reports current state - this is normal idempotent behavior
- **Verification**: Check audit logs to confirm operations completed successfully
### Role Synchronization Issues
- **Check audit log** for role assignment/removal details
- **Verify CSV format** for role columns
- **Use dry-run mode** to preview role changes
### API Rate Limiting
- Built-in 100ms delays between operations
- Monitor API response times in console output
- Consider breaking large CSV files into smaller batches
@@ -441,6 +519,7 @@ cat dry-run-audit.log
## Development
### Running Tests
Set your .env with the required variables or make them part of your environment:
```bash
@@ -476,10 +555,12 @@ GOOS=windows GOARCH=amd64 go build -o cognito-permit-sync.exe
- **✅ CSV Comments**: Support for comment lines with #
- **✅ Better Error Handling**: Graceful handling of missing/existing users
- **✅ Performance Insights**: Detailed timing and success metrics
- **🆕 User Disable/Enable**: Temporarily disable users without deletion
- **🆕 Enhanced Access Control**: Fine-grained user access management
- **🆕 Extended Audit Trail**: Track all disable/enable operations
- **🆕 Idempotent Disable/Enable**: Smart state checking prevents unnecessary operations
- ** User Disable/Enable**: Temporarily disable users without deletion
- ** Enhanced Access Control**: Fine-grained user access management
- ** Extended Audit Trail**: Track all disable/enable operations
- ** Idempotent Disable/Enable**: Smart state checking prevents unnecessary operations
- **🆕 Scoped API Key Support**: Use `-project use-key` to auto-detect project/environment from API key
- **🆕 Cross-Project User Sync**: Sync existing Cognito users to new Permit.io projects
## Future Enhancements
@@ -1,3 +1,3 @@
email,first_name,last_name,admin,viewer
"foo1@gmail.com",john, smith,editor,viewer
"foo2@gmail.com",jane, smith,editor,viewer, admin
"foo1@gmail.com",john, smith,auditor,user_admin
"foo2@gmail.com",jane, smith,client_user,super_admin
1 email first_name last_name admin viewer
2 foo1@gmail.com john smith editor auditor viewer user_admin
3 foo2@gmail.com jane smith editor client_user viewer super_admin