diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index 2c13b7c609..1526aaa7ef 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -64,11 +64,15 @@ services: UNKEY_REDIS_URL: "redis://redis:6379" UNKEY_DATABASE_PRIMARY: "unkey:password@tcp(mysql:3306)/unkey?parseTime=true" UNKEY_CLICKHOUSE_URL: "clickhouse://default:password@clickhouse:9000?secure=false&skip_verify=true" - UNKEY_CHPROXY_ENABLED: "true" UNKEY_CHPROXY_AUTH_TOKEN: "chproxy-test-token-123" UNKEY_OTEL: true OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel:4318" OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf" + VAULT_S3_URL: "http://s3:3902" + VAULT_S3_BUCKET: "vault" + VAULT_S3_ACCESS_KEY_ID: "minio_root_user" + VAULT_S3_ACCESS_KEY_SECRET: "minio_root_password" + VAULT_MASTER_KEYS: "Ch9rZWtfMmdqMFBJdVhac1NSa0ZhNE5mOWlLSnBHenFPENTt7an5MRogENt9Si6wms4pQ2XIvqNSIgNpaBenJmXgcInhu6Nfv2U=" # UNKEY_PROMETHEUS_PORT: 2112 redis: diff --git a/go/apps/api/config.go b/go/apps/api/config.go index 8b0c321799..a894a3b280 100644 --- a/go/apps/api/config.go +++ b/go/apps/api/config.go @@ -75,9 +75,6 @@ type Config struct { // --- ClickHouse proxy configuration --- - // ChproxyEnabled enables the ClickHouse proxy endpoints - ChproxyEnabled bool - // ChproxyToken is the authentication token for ClickHouse proxy endpoints ChproxyToken string } diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index f29414e91f..c6c057d419 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -417,33 +417,6 @@ type RatelimitResponse struct { Name string `json:"name"` } -// Role defines model for Role. -type Role struct { - // Description Optional detailed explanation of what this role encompasses and what access it provides. - // Helps team members understand the role's scope, intended use cases, and security implications. - // Include information about what types of users should receive this role and what they can accomplish. - // Not visible to end users - this is for internal documentation and access control audits. - Description *string `json:"description,omitempty"` - - // Id The unique identifier for this role within Unkey's system. - // Generated automatically when the role is created and used to reference this role in API operations. - // Always begins with 'role_' followed by alphanumeric characters and underscores. - Id string `json:"id"` - - // Name The human-readable name for this role that describes its function. - // Should be descriptive enough for administrators to understand what access this role provides. - // Use clear, semantic names that reflect the job function or responsibility level. - // Names must be unique within your workspace to avoid confusion during role assignment. - Name string `json:"name"` - - // Permissions Complete list of permissions currently assigned to this role. - // Each permission grants specific access rights that will be inherited by any keys or users assigned this role. - // Use this list to understand the full scope of access provided by this role. - // Permissions can be added or removed from roles without affecting the role's identity or other properties. - // Empty array indicates a role with no permissions currently assigned. - Permissions []Permission `json:"permissions"` -} - // UnauthorizedErrorResponse Error response when authentication has failed or credentials are missing. This occurs when: // - No authentication token is provided in the request // - The provided token is invalid, expired, or malformed @@ -1931,6 +1904,33 @@ type VerifyKeyRatelimitData struct { Reset int64 `json:"reset"` } +// Role defines model for role. +type Role struct { + // Description Optional detailed explanation of what this role encompasses and what access it provides. + // Helps team members understand the role's scope, intended use cases, and security implications. + // Include information about what types of users should receive this role and what they can accomplish. + // Not visible to end users - this is for internal documentation and access control audits. + Description *string `json:"description,omitempty"` + + // Id The unique identifier for this role within Unkey's system. + // Generated automatically when the role is created and used to reference this role in API operations. + // Always begins with 'role_' followed by alphanumeric characters and underscores. + Id string `json:"id"` + + // Name The human-readable name for this role that describes its function. + // Should be descriptive enough for administrators to understand what access this role provides. + // Use clear, semantic names that reflect the job function or responsibility level. + // Names must be unique within your workspace to avoid confusion during role assignment. + Name string `json:"name"` + + // Permissions Complete list of permissions currently assigned to this role. + // Each permission grants specific access rights that will be inherited by any keys or users assigned this role. + // Use this list to understand the full scope of access provided by this role. + // Permissions can be added or removed from roles without affecting the role's identity or other properties. + // Empty array indicates a role with no permissions currently assigned. + Permissions []Permission `json:"permissions"` +} + // ChproxyMetricsJSONRequestBody defines body for ChproxyMetrics for application/json ContentType. type ChproxyMetricsJSONRequestBody = ChproxyMetricsRequestBody diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index a9ca263690..eec2b35fb1 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -2807,53 +2807,11 @@ components: type: object properties: role: - "$ref": "#/components/schemas/Role" + "$ref": "#/components/schemas/role" required: - role additionalProperties: false description: Complete role details including assigned permissions. - Role: - type: object - properties: - id: - type: string - description: | - The unique identifier for this role within Unkey's system. - Generated automatically when the role is created and used to reference this role in API operations. - Always begins with 'role_' followed by alphanumeric characters and underscores. - example: role_1234567890abcdef - name: - type: string - description: | - The human-readable name for this role that describes its function. - Should be descriptive enough for administrators to understand what access this role provides. - Use clear, semantic names that reflect the job function or responsibility level. - Names must be unique within your workspace to avoid confusion during role assignment. - example: "support.readonly" - description: - type: string - description: | - Optional detailed explanation of what this role encompasses and what access it provides. - Helps team members understand the role's scope, intended use cases, and security implications. - Include information about what types of users should receive this role and what they can accomplish. - Not visible to end users - this is for internal documentation and access control audits. - example: "Provides read-only access for customer support representatives to view user accounts and support tickets" - permissions: - type: array - items: - "$ref": "#/components/schemas/Permission" - maxItems: 100 - description: | - Complete list of permissions currently assigned to this role. - Each permission grants specific access rights that will be inherited by any keys or users assigned this role. - Use this list to understand the full scope of access provided by this role. - Permissions can be added or removed from roles without affecting the role's identity or other properties. - Empty array indicates a role with no permissions currently assigned. - required: - - id - - name - - permissions - additionalProperties: false V2PermissionsListPermissionsResponseData: type: array maxItems: 1000 @@ -2865,7 +2823,7 @@ components: maxItems: 1000 description: Array of roles with their assigned permissions. items: - "$ref": "#/components/schemas/Role" + "$ref": "#/components/schemas/role" V2RatelimitDeleteOverrideResponseData: type: object additionalProperties: false @@ -3841,7 +3799,7 @@ paths: x-speakeasy-name-override: listIdentities x-speakeasy-pagination: inputs: - - in: parameters + - in: requestBody name: cursor type: cursor outputs: @@ -5892,6 +5850,19 @@ security: - rootKey: [] servers: - url: https://api.unkey.com +tags: + - description: API management operations + name: apis + - description: Identity management operations + name: identities + - description: API key management operations + name: keys + - description: Health check operations + name: liveness + - description: Permission and role management operations + name: permissions + - description: Rate limiting operations + name: ratelimit x-speakeasy-retries: backoff: exponent: 1.5 diff --git a/go/apps/api/openapi/openapi-split.yaml b/go/apps/api/openapi/openapi-split.yaml index 8f85e8b985..513f4eab6e 100644 --- a/go/apps/api/openapi/openapi-split.yaml +++ b/go/apps/api/openapi/openapi-split.yaml @@ -97,6 +97,20 @@ x-speakeasy-retries: security: - rootKey: [] +tags: + - name: apis + description: API management operations + - name: identities + description: Identity management operations + - name: keys + description: API key management operations + - name: liveness + description: Health check operations + - name: permissions + description: Permission and role management operations + - name: ratelimit + description: Rate limiting operations + paths: # Health Endpoints /v2/liveness: diff --git a/go/apps/api/openapi/spec/paths/v2/identities/listIdentities/index.yaml b/go/apps/api/openapi/spec/paths/v2/identities/listIdentities/index.yaml index 2b90c840bb..1fa9a77e06 100644 --- a/go/apps/api/openapi/spec/paths/v2/identities/listIdentities/index.yaml +++ b/go/apps/api/openapi/spec/paths/v2/identities/listIdentities/index.yaml @@ -103,7 +103,7 @@ post: type: cursor inputs: - name: cursor - in: parameters + in: requestBody type: cursor outputs: nextCursor: "$.data.cursor" diff --git a/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml b/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml index 7d6c268a18..b67f1238f7 100644 --- a/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml +++ b/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml @@ -1,7 +1,7 @@ type: object properties: role: - "$ref": "../../../../common/Role.yaml" + "$ref": "../../../../common/role.yaml" required: - role additionalProperties: false diff --git a/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml b/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml index 31fb8044ea..e0c4bf1f5a 100644 --- a/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml +++ b/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml @@ -2,4 +2,4 @@ type: array maxItems: 1000 description: Array of roles with their assigned permissions. items: - "$ref": "../../../../common/Role.yaml" + "$ref": "../../../../common/role.yaml" diff --git a/go/apps/api/routes/register.go b/go/apps/api/routes/register.go index 2a11a12604..28414f9dd7 100644 --- a/go/apps/api/routes/register.go +++ b/go/apps/api/routes/register.go @@ -74,7 +74,7 @@ func Register(srv *zen.Server, svc *Services) { // --------------------------------------------------------------------------- // chproxy (internal endpoints) - if svc.ChproxyEnabled { + if svc.ChproxyToken != "" { // chproxy/verifications - internal endpoint for key verification events srv.RegisterRoute([]zen.Middleware{ withTracing, diff --git a/go/apps/api/routes/services.go b/go/apps/api/routes/services.go index 256e19ea35..03410fdefb 100644 --- a/go/apps/api/routes/services.go +++ b/go/apps/api/routes/services.go @@ -18,15 +18,14 @@ type EventBuffer interface { } type Services struct { - Logger logging.Logger - Database db.Database - Keys keys.KeyService - ClickHouse clickhouse.ClickHouse - Validator *validation.Validator - Ratelimit ratelimit.Service - Auditlogs auditlogs.AuditLogService - Caches caches.Caches - Vault *vault.Service - ChproxyEnabled bool - ChproxyToken string + Logger logging.Logger + Database db.Database + Keys keys.KeyService + ClickHouse clickhouse.ClickHouse + Validator *validation.Validator + Ratelimit ratelimit.Service + Auditlogs auditlogs.AuditLogService + Caches caches.Caches + Vault *vault.Service + ChproxyToken string } diff --git a/go/apps/api/run.go b/go/apps/api/run.go index 8e58a93e59..01b9b44d8a 100644 --- a/go/apps/api/run.go +++ b/go/apps/api/run.go @@ -217,17 +217,16 @@ func Run(ctx context.Context, cfg Config) error { }) routes.Register(srv, &routes.Services{ - Logger: logger, - Database: db, - ClickHouse: ch, - Keys: keySvc, - Validator: validator, - Ratelimit: rlSvc, - Auditlogs: auditlogSvc, - Caches: caches, - Vault: vaultSvc, - ChproxyEnabled: cfg.ChproxyEnabled, - ChproxyToken: cfg.ChproxyToken, + Logger: logger, + Database: db, + ClickHouse: ch, + Keys: keySvc, + Validator: validator, + Ratelimit: rlSvc, + Auditlogs: auditlogSvc, + Caches: caches, + Vault: vaultSvc, + ChproxyToken: cfg.ChproxyToken, }) if cfg.Listener == nil { // Create listener from HttpPort (production) diff --git a/go/benchmarks/keyverify.js b/go/benchmarks/keyverify.js index 4c8b4f7823..8aa6d314aa 100644 --- a/go/benchmarks/keyverify.js +++ b/go/benchmarks/keyverify.js @@ -58,6 +58,7 @@ const headers = { Authorization: `Bearer ${UNKEY_ROOT_KEY}`, }; +// biome-ignore lint/style/noDefaultExport: k6 needs a default export export default function () { const response = Math.random() < 0.5 diff --git a/go/benchmarks/ratelimit.js b/go/benchmarks/ratelimit.js index 30c7bd67b2..e31f2c883f 100644 --- a/go/benchmarks/ratelimit.js +++ b/go/benchmarks/ratelimit.js @@ -55,6 +55,7 @@ const headers = { }; const identifiers = ["user1", "user2", "user3", "user4", "user5"]; +// biome-ignore lint/style/noDefaultExport: k6 needs a default exporet export default function () { // Randomly choose between v1 and v2 (50/50 split) diff --git a/go/cmd/api/main.go b/go/cmd/api/main.go index 183e5f3b30..982773e81e 100644 --- a/go/cmd/api/main.go +++ b/go/cmd/api/main.go @@ -62,23 +62,16 @@ var Cmd = &cli.Command{ // Vault Configuration cli.StringSlice("vault-master-keys", "Vault master keys for encryption", cli.EnvVar("UNKEY_VAULT_MASTER_KEYS")), - - // S3 Configuration cli.String("vault-s3-url", "S3 Compatible Endpoint URL", cli.EnvVar("UNKEY_VAULT_S3_URL")), cli.String("vault-s3-bucket", "S3 bucket name", cli.EnvVar("UNKEY_VAULT_S3_BUCKET")), cli.String("vault-s3-access-key-id", "S3 access key ID", cli.EnvVar("UNKEY_VAULT_S3_ACCESS_KEY_ID")), - cli.String("vault-s3-secret-access-key", "S3 secret access key", - cli.EnvVar("UNKEY_VAULT_S3_SECRET_ACCESS_KEY")), + cli.String("vault-s3-access-key-secret", "S3 secret access key", + cli.EnvVar("UNKEY_VAULT_S3_ACCESS_KEY_SECRET")), // ClickHouse Proxy Service Configuration - cli.Bool( - "chproxy-enabled", - "Enable ClickHouse proxy endpoints for high-throughput event collection", - cli.EnvVar("UNKEY_CHPROXY_ENABLED"), - ), cli.String( "chproxy-auth-token", "Authentication token for ClickHouse proxy endpoints. Required when proxy is enabled.", @@ -113,7 +106,7 @@ func action(ctx context.Context, cmd *cli.Command) error { URL: cmd.String("vault-s3-url"), Bucket: cmd.String("vault-s3-bucket"), AccessKeyID: cmd.String("vault-s3-access-key-id"), - SecretAccessKey: cmd.String("vault-s3-secret-access-key"), + SecretAccessKey: cmd.String("vault-s3-access-key-secret"), } } @@ -152,8 +145,7 @@ func action(ctx context.Context, cmd *cli.Command) error { VaultS3: vaultS3Config, // ClickHouse proxy configuration - ChproxyEnabled: cmd.Bool("chproxy-enabled"), - ChproxyToken: cmd.String("chproxy-auth-token"), + ChproxyToken: cmd.String("chproxy-auth-token"), } err := config.Validate() diff --git a/go/pkg/codes/constants_gen.go b/go/pkg/codes/constants_gen.go index 4fce01b97d..70deeca842 100644 --- a/go/pkg/codes/constants_gen.go +++ b/go/pkg/codes/constants_gen.go @@ -6,20 +6,20 @@ type URN string // Error code constants for use in switch statements for exhaustive checking const ( -// ---------------- -// UserErrors -// ---------------- + // ---------------- + // UserErrors + // ---------------- -// BadRequest + // BadRequest // PermissionsQuerySyntaxError indicates a syntax or lexical error in verifyKey permissions query parsing. UserErrorsBadRequestPermissionsQuerySyntaxError URN = "err:user:bad_request:permissions_query_syntax_error" -// ---------------- -// UnkeyAuthErrors -// ---------------- + // ---------------- + // UnkeyAuthErrors + // ---------------- -// Authentication + // Authentication // Missing indicates authentication credentials were not provided. UnkeyAuthErrorsAuthenticationMissing URN = "err:unkey:authentication:missing" @@ -28,7 +28,7 @@ const ( // KeyNotFound indicates the authentication key was not found. UnkeyAuthErrorsAuthenticationKeyNotFound URN = "err:unkey:authentication:key_not_found" -// Authorization + // Authorization // InsufficientPermissions indicates the authenticated entity lacks // sufficient permissions for the requested operation. @@ -40,92 +40,91 @@ const ( // WorkspaceDisabled indicates the associated workspace is disabled. UnkeyAuthErrorsAuthorizationWorkspaceDisabled URN = "err:unkey:authorization:workspace_disabled" -// ---------------- -// UnkeyDataErrors -// ---------------- + // ---------------- + // UnkeyDataErrors + // ---------------- -// Key + // Key // NotFound indicates the requested key was not found. UnkeyDataErrorsKeyNotFound URN = "err:unkey:data:key_not_found" -// Workspace + // Workspace // NotFound indicates the requested workspace was not found. UnkeyDataErrorsWorkspaceNotFound URN = "err:unkey:data:workspace_not_found" -// Api + // Api // NotFound indicates the requested API was not found. UnkeyDataErrorsApiNotFound URN = "err:unkey:data:api_not_found" -// Permission + // Permission // Duplicate indicates the requested permission already exists. UnkeyDataErrorsPermissionDuplicate URN = "err:unkey:data:permission_already_exists" // NotFound indicates the requested permission was not found. UnkeyDataErrorsPermissionNotFound URN = "err:unkey:data:permission_not_found" -// Role + // Role // Duplicate indicates the requested role already exists. UnkeyDataErrorsRoleDuplicate URN = "err:unkey:data:role_already_exists" // NotFound indicates the requested role was not found. UnkeyDataErrorsRoleNotFound URN = "err:unkey:data:role_not_found" -// KeyAuth + // KeyAuth // NotFound indicates the requested key authentication was not found. UnkeyDataErrorsKeyAuthNotFound URN = "err:unkey:data:key_auth_not_found" -// RatelimitNamespace + // RatelimitNamespace // NotFound indicates the requested rate limit namespace was not found. UnkeyDataErrorsRatelimitNamespaceNotFound URN = "err:unkey:data:ratelimit_namespace_not_found" -// RatelimitOverride + // RatelimitOverride // NotFound indicates the requested rate limit override was not found. UnkeyDataErrorsRatelimitOverrideNotFound URN = "err:unkey:data:ratelimit_override_not_found" -// Identity + // Identity // NotFound indicates the requested identity was not found. UnkeyDataErrorsIdentityNotFound URN = "err:unkey:data:identity_not_found" // Duplicate indicates the requested identity already exists. UnkeyDataErrorsIdentityDuplicate URN = "err:unkey:data:identity_already_exists" -// AuditLog + // AuditLog // NotFound indicates the requested audit log was not found. UnkeyDataErrorsAuditLogNotFound URN = "err:unkey:data:audit_log_not_found" -// ---------------- -// UnkeyAppErrors -// ---------------- + // ---------------- + // UnkeyAppErrors + // ---------------- -// Internal + // Internal // UnexpectedError represents an unhandled or unexpected error condition. UnkeyAppErrorsInternalUnexpectedError URN = "err:unkey:application:unexpected_error" // ServiceUnavailable indicates a service is temporarily unavailable. UnkeyAppErrorsInternalServiceUnavailable URN = "err:unkey:application:service_unavailable" -// Validation + // Validation // InvalidInput indicates a client provided input that failed validation. UnkeyAppErrorsValidationInvalidInput URN = "err:unkey:application:invalid_input" // AssertionFailed indicates a runtime assertion or invariant check failed. UnkeyAppErrorsValidationAssertionFailed URN = "err:unkey:application:assertion_failed" -// Protection + // Protection // ProtectedResource indicates an attempt to modify a protected resource. UnkeyAppErrorsProtectionProtectedResource URN = "err:unkey:application:protected_resource" -// Precondition + // Precondition // PreconditionFailed indicates a precondition check failed. UnkeyAppErrorsPreconditionPreconditionFailed URN = "err:unkey:application:precondition_failed" - )