Merged in feature/cognito-shared-environments (pull request #211)

cognito and permit shared prod environment support

* code complete

* user creattion tool

test harness
This commit is contained in:
Jay Brown
2026-02-26 12:33:35 +00:00
parent 6dccf494f8
commit c668485e6f
55 changed files with 3721 additions and 381 deletions
+1
View File
@@ -104,6 +104,7 @@ graph TB
### Security Architecture
- **Authentication**: OAuth2 with AWS Cognito integration and MFA support
- **Authorization**: Enhanced RBAC with Permit.io integration and group-based permissions
- **Multi-Environment Isolation**: Multiple customer environments can share a single Cognito user pool. Each server instance is scoped via `COGNITO_ENVIRONMENT_ID`, and users are tagged with a `custom:environment_id` attribute. Middleware enforces that users can only access the environment they belong to (super_admin users are exempt).
- **Data Protection**: Encrypted storage (S3 server-side encryption) with folder path preservation
- **Network Security**: VPC isolation and security groups
- **API Security**: JWT validation and OpenAPI request validation with enhanced token handling
+20 -2
View File
@@ -1024,11 +1024,13 @@ Creates a new user in both AWS Cognito and Permit.io systems with optional role
**Security**: Requires JWT authentication
**Environment Scoping**: When `COGNITO_ENVIRONMENT_ID` is configured on the server, newly created users are automatically tagged with the `custom:environment_id` attribute matching the server's value. This ensures users are scoped to the correct environment.
**Creation Flow**:
1. User is created in AWS Cognito (primary authentication system)
1. User is created in AWS Cognito (primary authentication system) with the server's environment ID (if configured)
2. If Cognito creation fails, operation fails with HTTP 502
3. User is created in Permit.io (authorization/role system)
3. User is created in Permit.io (authorization/role system) with the environment ID as an attribute
4. If Permit.io creation fails, user still exists in Cognito but `permit_synced=false`
5. Roles are assigned in Permit.io (best-effort, continues on failures)
@@ -1081,6 +1083,8 @@ Lists users from AWS Cognito with pagination, filtering, and sorting support.
**Security**: Requires JWT authentication
**Environment Filtering**: When `COGNITO_ENVIRONMENT_ID` is configured, the response is filtered to include only users whose `custom:environment_id` matches the server's value or users with no environment tag (legacy/untagged users). Users belonging to other environments are excluded from the results.
**Query Parameters**:
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
@@ -1121,6 +1125,8 @@ Retrieves user details from both AWS Cognito and Permit.io by email address.
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment to prevent information leakage. Untagged users and users matching the server's environment are accessible.
**Path Parameters**:
- `email`: URL-encoded user email address (max 320 chars)
@@ -1133,6 +1139,8 @@ Checks if a user exists in Cognito and/or Permit.io without returning full detai
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404` for users belonging to a different environment.
**Response Headers**:
- `X-User-Exists-Cognito`: boolean
@@ -1149,6 +1157,8 @@ Updates user attributes (first_name, last_name) in Cognito and manages role assi
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment.
**Role Management (REPLACE Strategy)**:
- Providing a `roles` array REPLACES all existing roles (not additive)
@@ -1175,6 +1185,8 @@ Permanently deletes a user from both AWS Cognito and Permit.io. This operation i
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment.
**Query Parameters**:
- `confirm` (required): Must be `true` to confirm deletion
@@ -1200,6 +1212,8 @@ Disables a user in AWS Cognito, preventing authentication. User data and roles a
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment.
**Response** (`AdminUserActionResponse`):
```json
@@ -1218,6 +1232,8 @@ Re-enables a previously disabled user in AWS Cognito. Restores authentication ca
**Security**: Requires JWT authentication
**Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment.
**Response**: Same as disable endpoint with `"action": "enable"`
---
@@ -1460,6 +1476,8 @@ Returns a comprehensive compliance report showing which users have and have not
**Security**: Requires JWT authentication
**Environment Filtering**: When `COGNITO_ENVIRONMENT_ID` is configured, the compliance report only includes users matching the server's environment or untagged legacy users. Users from other environments are excluded from the total counts and user list.
**Query Parameters**:
| Parameter | Type | Default | Description |
+4 -2
View File
@@ -61,13 +61,15 @@ internal/
└── export/ # Data export operations
```
#### Infrastructure Services
#### Infrastructure Services
```
internal/
├── database/ # Data persistence layer
├── server/ # HTTP and queue server infrastructure
├── serviceconfig/ # Configuration management
├── cognitoauth/ # Authentication middleware
├── cognitoauth/ # Authentication middleware (includes environment access control)
├── usermanagement/ # Cognito and Permit.io user operations, environment filtering
├── eula/ # EULA version management and compliance
├── test/ # Testing utilities
└── validation/ # Input validation
```
@@ -82,6 +82,7 @@ From `internal/serviceconfig/auth/config.go`:
- `NO_JWT_VALIDATION` (default `false`)
- `PERMIT_IO_API_KEY` (optional but required for full Permit.io operation)
- `PERMIT_IO_PDP_URL` (default `http://localhost:7766`)
- `COGNITO_ENVIRONMENT_ID` (optional, default empty) - Scopes this server instance to a specific environment within a shared Cognito user pool. When set, only users tagged with this environment ID (or untagged legacy users) are visible to admin operations and EULA compliance. Must be lowercase alphanumeric with underscores allowed in middle positions, max 50 characters. Empty string means no scoping (backward compatible).
Auth path customization:
- `COGNITO_LOGIN_PATH` (default `/login`)
+37
View File
@@ -163,6 +163,18 @@ curl http://localhost:8082/health # docInitRunner
# ... etc
```
The queryAPI health response includes `version`, `buildTime`, and `commit` fields. When `COGNITO_ENVIRONMENT_ID` is configured, an additional `cognitoEnvironmentId` field is included in the response, allowing operators to verify which environment the server instance is scoped to:
```json
{
"status": "healthy",
"version": "1.0.0",
"buildTime": "2025-01-15T10:00:00Z",
"commit": "abc1234",
"cognitoEnvironmentId": "acmehealth"
}
```
#### Health Check Implementation
```go
func (h *HealthHandler) IsHealthy(ctx echo.Context) error {
@@ -563,6 +575,31 @@ Queue consumer services require SQS permissions:
}
```
### Cognito Permissions (Environment Isolation)
When `COGNITO_ENVIRONMENT_ID` is configured, the queryAPI service automatically registers the `custom:environment_id` attribute on the Cognito user pool at startup. This requires additional IAM permissions:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CognitoEnvironmentAttributeRegistration",
"Effect": "Allow",
"Action": [
"cognito-idp:DescribeUserPool",
"cognito-idp:AddCustomAttributes"
],
"Resource": [
"arn:aws:cognito-idp:${REGION}:${ACCOUNT_ID}:userpool/${USER_POOL_ID}"
]
}
]
}
```
The `DescribeUserPool` permission is used to check if the attribute already exists. The `AddCustomAttributes` permission is only used when the attribute needs to be registered for the first time. Once registered, the attribute persists permanently on the pool (Cognito custom attributes cannot be removed).
### Legacy Textract Permissions
The prior text extraction/query pipeline is deprecated. Keep Textract IAM permissions only if you still run legacy compatibility services in your deployment.
@@ -460,6 +460,17 @@ Error responses typically include a `message` field, but the structure varies by
**Recommended error handling**: Check for both `error` and `message` fields when parsing error responses.
### Environment Access Denied (403)
When the server is configured with `COGNITO_ENVIRONMENT_ID`, an additional access control check runs after JWT validation. Users whose Cognito `custom:environment_id` attribute does not match the server's configured value will receive a `403 Forbidden` response with the message `"environment access denied"`. This applies to all authenticated endpoints.
Exceptions:
- Users with the `super_admin` role bypass environment checks entirely.
- Users with no `custom:environment_id` attribute (legacy/untagged users) are allowed through for backward compatibility.
- If the server has no `COGNITO_ENVIRONMENT_ID` set, this check is skipped entirely.
The environment check result is cached per user for 30 minutes. If a user's environment assignment changes, the new assignment takes effect after the cache expires.
### Handling Authentication Errors (401)
```typescript
@@ -159,6 +159,7 @@ queryapi.RegisterHandlers(cfg.Router, cons)
| `permitio.go` | Permit.io authorization client |
| `token.go` | JWT token operations (refresh, validation) |
| `validation.go` | Token signature and claims validation |
| `envcheck.go` | Environment access control check with in-memory cache (30min TTL) |
**Authentication Flow**:
```mermaid