@hoanghainh/komagu 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/assess/lsa/CHANGELOG.md +6 -0
- package/assess/lsa/VERSION +1 -1
- package/assess/lsa/adapters/build_adapters.py +16 -8
- package/assess/lsa/adapters/codex/README.md +1 -1
- package/assess/lsa/adapters/generic/README.md +1 -1
- package/assess/lsa/knowledge/severity-and-cwe.md +1 -1
- package/assess/lsa/scripts/baseline.py +5 -2
- package/assess/lsa/scripts/findings_to_sarif.py +24 -1
- package/assess/lsa/tests/test_adapters.py +21 -2
- package/assess/lsa/tests/test_baseline.py +8 -0
- package/assess/lsa/tests/test_contract_groundwork.py +7 -7
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/scan.py +2 -7
- package/operate/cli/commands/test.py +1 -1
- package/operate/cli/env.py +4 -20
- package/operate/cross_user_tester/persona_matrix.py +5 -1
- package/operate/devsecops/pipeline.py +5 -5
- package/operate/exploit/generator.py +5 -1
- package/operate/lsa_sarif/lsa_to_sarif.py +24 -2
- package/operate/lsa_sarif/sarif_findings.py +96 -0
- package/operate/remediator/runner.py +1 -5
- package/operate/remediator/strategies.py +1 -1
- package/operate/tests/test_cli_baseline.py +1 -9
- package/operate/tests/test_cli_env.py +8 -29
- package/operate/tests/test_cross_user_pipeline.py +2 -2
- package/operate/tests/test_exploit_and_virtual_patch.py +3 -3
- package/operate/tests/test_npm_package.py +2 -16
- package/operate/tests/test_sarif_from_scan.py +156 -0
- package/package.json +2 -4
- package/remediate/authz/README.md +1 -1
- package/remediate/authz/dotnet/src/Komagu.Security.Authorization/DefaultKomaguAuthorizationService.cs +1 -1
- package/remediate/authz/dotnet/src/Komagu.Security.Authorization/SecurityAuditEvent.cs +2 -2
- package/remediate/authz/golang/authz.go +1 -1
- package/remediate/authz/nodejs/src/service.js +1 -1
- package/remediate/authz/nodejs/tests/test-authz.js +1 -1
- package/remediate/authz/patcher.py +3 -4
- package/remediate/authz/python/komagu_authz/models.py +2 -2
- package/remediate/authz/python/tests/test_fastapi_adapter.py +1 -1
- package/remediate/authz/python/tests/test_fastapi_d5_crosstest.py +1 -1
- package/remediate/authz/python/tests/test_python_authz.py +3 -3
- package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/DefaultKomaguSecurityManager.kt +1 -1
- package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/SecurityAuditEvent.kt +1 -2
- package/remediate/authz/tests/test_bola_patcher.py +1 -1
- package/remediate/edge/coraza/rules/coraza-crs-custom.conf +3 -4
- package/remediate/edge/coraza/virtual_patcher.py +4 -6
- package/remediate/edge/docker-compose.yml +1 -1
- package/remediate/ekyc/server/jpki_verifier.py +2 -8
- package/remediate/ekyc/server/tokenization.py +2 -8
- package/remediate/identity/README.md +5 -5
- package/remediate/identity/dotnet/src/Komagu.Security.Identity/KomaguOidcOptions.cs +2 -2
- package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TestTokenGenerator.cs +1 -1
- package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TokenValidationTests.cs +1 -1
- package/remediate/identity/python/komagu_identity/oidc_validator.py +2 -8
- package/remediate/identity/python/tests/test_oidc_validator.py +1 -1
- package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/KomaguOidcProperties.kt +2 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/KomaguIdentityAutoConfigurationTest.kt +2 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TestJwtFactory.kt +1 -1
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TokenValidationIntegrationTest.kt +1 -1
- package/remediate/identity/test-env/realm-komagu.json +1 -1
- package/remediate/legacy_shims/coldfusion/KomaguSecurityShim.cfc +5 -5
- package/remediate/query/README.md +1 -1
- package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/DiagnosticIds.cs +1 -1
- package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/Komagu.Security.Query.Analyzers.csproj +1 -1
- package/remediate/query/spring/src/main/kotlin/io/komagu/security/query/detekt/KomaguRuleSetProvider.kt +1 -1
- package/remediate/resilience/dr/dr_manager.py +2 -8
- package/remediate/resilience/dr/runbook.md +2 -2
- package/remediate/resilience/terraform/variables.tf +1 -1
- package/remediate/tenancy/audit/audit_trail.py +2 -8
- package/remediate/tenancy/crypto/byok_manager.py +2 -8
- package/remediate/tenancy/middleware/tenant_context.py +2 -8
- package/remediate/tenancy/sql/rls_isolation.sql +11 -11
- package/remediate/tenancy/sql/schema_per_tenant.sql +7 -7
- package/remediate/tenancy/test_sql_postgres.py +1 -1
- package/remediate/tenancy/tests/test_tenancy.py +4 -4
- package/bin/komainu +0 -16
- package/bin/komainu-legacy.js +0 -16
- package/remediate/ekyc/tests/test_legacy_env.py +0 -33
- package/remediate/identity/python/tests/test_legacy_env.py +0 -33
- package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessor.kt +0 -60
- package/remediate/identity/spring/src/main/resources/META-INF/spring.factories +0 -2
- package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessorTest.kt +0 -86
- package/remediate/resilience/tests/test_legacy_env.py +0 -33
- package/remediate/tenancy/tests/test_legacy_env.py +0 -49
|
@@ -22,18 +22,12 @@ import re
|
|
|
22
22
|
import secrets
|
|
23
23
|
import sys
|
|
24
24
|
import time
|
|
25
|
-
import warnings
|
|
26
25
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
30
|
-
"""Read KOMAGU_<name
|
|
31
|
-
|
|
32
|
-
if value is None:
|
|
33
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
34
|
-
if value is not None:
|
|
35
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
36
|
-
return default if value is None else value
|
|
29
|
+
"""Read KOMAGU_<name>."""
|
|
30
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
37
31
|
|
|
38
32
|
|
|
39
33
|
# Allowed fields in API requests under Zero-PII contract
|
|
@@ -15,7 +15,7 @@ Every incoming request bearing an access token is validated against 5 mandatory
|
|
|
15
15
|
| **Missing Token** | No `Authorization: Bearer <token>` header supplied. | **HTTP 401 Unauthorized** |
|
|
16
16
|
| **Token Expiry** | Current timestamp > `exp` claim (`ClockSkew` enforced to 0s or strict delta). | **HTTP 401 Unauthorized** |
|
|
17
17
|
| **Issuer Validation** | Token `iss` claim does not match configured trusted IdP authority. | **HTTP 401 Unauthorized** |
|
|
18
|
-
| **Audience Validation** | Token `aud` claim does not include the target resource server ID (`
|
|
18
|
+
| **Audience Validation** | Token `aud` claim does not include the target resource server ID (`komagu-api`). | **HTTP 401 Unauthorized** |
|
|
19
19
|
| **Cryptographic Signature** | Token signed by untrusted key or payload tampered with. | **HTTP 401 Unauthorized** |
|
|
20
20
|
|
|
21
21
|
---
|
|
@@ -36,7 +36,7 @@ Keycloak starts at `http://localhost:8080` with the `komagu` realm pre-loaded:
|
|
|
36
36
|
- **Realm Discovery URL:** `http://localhost:8080/realms/komagu/.well-known/openid-configuration`
|
|
37
37
|
- **Clients:**
|
|
38
38
|
- `komagu-portal`: Public SPA client (Standard Flow + PKCE enforced)
|
|
39
|
-
- `
|
|
39
|
+
- `komagu-api`: Resource server bearer-only client (`aud`: `komagu-api`)
|
|
40
40
|
- **Test Personas:**
|
|
41
41
|
- `alice` (Password: `password123`, Tenant: `tenant-alpha`, Role: `Member`)
|
|
42
42
|
- `bob` (Password: `password123`, Tenant: `tenant-alpha`, Role: `Member`)
|
|
@@ -58,7 +58,7 @@ var builder = WebApplication.CreateBuilder(args);
|
|
|
58
58
|
builder.Services.AddKomaguJwtAuthentication(options =>
|
|
59
59
|
{
|
|
60
60
|
options.Issuer = builder.Configuration["Authentication:Issuer"] ?? "https://auth.komagu.test/realms/komagu";
|
|
61
|
-
options.Audience = "
|
|
61
|
+
options.Audience = "komagu-api";
|
|
62
62
|
options.RequireHttpsMetadata = builder.Environment.IsProduction();
|
|
63
63
|
options.ClockSkew = TimeSpan.Zero;
|
|
64
64
|
});
|
|
@@ -82,13 +82,13 @@ komagu:
|
|
|
82
82
|
security:
|
|
83
83
|
oidc:
|
|
84
84
|
issuer: "https://auth.komagu.test/realms/komagu"
|
|
85
|
-
audience: "
|
|
85
|
+
audience: "komagu-api"
|
|
86
86
|
jwk-set-uri: "https://auth.komagu.test/realms/komagu/protocol/openid-connect/certs"
|
|
87
87
|
clock-skew-seconds: 0
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
The auto-configuration registers:
|
|
91
|
-
1. `KomaguAudienceValidator`: Enforces `aud ==
|
|
91
|
+
1. `KomaguAudienceValidator`: Enforces `aud == komagu-api`.
|
|
92
92
|
2. `KomaguJwtAuthenticationConverter`: Maps `sub` to principal and `tenant_id` claim to authentication details.
|
|
93
93
|
3. Stateless `SecurityFilterChain` permitting public endpoints and enforcing authentication on all other APIs.
|
|
94
94
|
|
|
@@ -13,10 +13,10 @@ public sealed class KomaguOidcOptions
|
|
|
13
13
|
public string Issuer { get; set; } = string.Empty;
|
|
14
14
|
|
|
15
15
|
/// <summary>
|
|
16
|
-
/// Expected resource server audience identifier (e.g. "
|
|
16
|
+
/// Expected resource server audience identifier (e.g. "komagu-api").
|
|
17
17
|
/// The pre-rename default is kept so issued tokens stay valid (#290).
|
|
18
18
|
/// </summary>
|
|
19
|
-
public string Audience { get; set; } = "
|
|
19
|
+
public string Audience { get; set; } = "komagu-api";
|
|
20
20
|
|
|
21
21
|
/// <summary>
|
|
22
22
|
/// OIDC well-known configuration endpoint URI.
|
package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TestTokenGenerator.cs
CHANGED
|
@@ -18,7 +18,7 @@ public sealed class TestTokenGenerator : IDisposable
|
|
|
18
18
|
|
|
19
19
|
public string GenerateToken(
|
|
20
20
|
string issuer = "https://auth.komagu.test/realms/komagu",
|
|
21
|
-
string audience = "
|
|
21
|
+
string audience = "komagu-api",
|
|
22
22
|
string userId = "user-alice",
|
|
23
23
|
string tenantId = "tenant-alpha",
|
|
24
24
|
DateTime? expires = null,
|
package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TokenValidationTests.cs
CHANGED
|
@@ -14,7 +14,7 @@ using Xunit;
|
|
|
14
14
|
public class TokenValidationTests : IDisposable
|
|
15
15
|
{
|
|
16
16
|
private const string ExpectedIssuer = "https://auth.komagu.test/realms/komagu";
|
|
17
|
-
private const string ExpectedAudience = "
|
|
17
|
+
private const string ExpectedAudience = "komagu-api";
|
|
18
18
|
|
|
19
19
|
private readonly TestTokenGenerator _tokenGenerator;
|
|
20
20
|
private readonly TestServer _server;
|
|
@@ -11,7 +11,6 @@ from __future__ import annotations
|
|
|
11
11
|
import math
|
|
12
12
|
import os
|
|
13
13
|
import sys
|
|
14
|
-
import warnings
|
|
15
14
|
from typing import Any, Dict, List, Optional, Union
|
|
16
15
|
|
|
17
16
|
import jwt
|
|
@@ -33,13 +32,8 @@ from .models import UserPrincipal
|
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
36
|
-
"""Read KOMAGU_<name
|
|
37
|
-
|
|
38
|
-
if value is None:
|
|
39
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
40
|
-
if value is not None:
|
|
41
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
42
|
-
return default if value is None else value
|
|
35
|
+
"""Read KOMAGU_<name>."""
|
|
36
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
43
37
|
|
|
44
38
|
|
|
45
39
|
# Standard test/dev personas matching Komagu cross-user test fixtures
|
|
@@ -44,7 +44,7 @@ class TestOidcTokenValidator(unittest.TestCase):
|
|
|
44
44
|
cls.attacker_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
45
45
|
|
|
46
46
|
cls.issuer = "https://identity.komagu.test"
|
|
47
|
-
cls.audience = "
|
|
47
|
+
cls.audience = "komagu-api"
|
|
48
48
|
|
|
49
49
|
def _create_token(
|
|
50
50
|
self,
|
|
@@ -13,10 +13,10 @@ data class KomaguOidcProperties(
|
|
|
13
13
|
var issuer: String = "",
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Expected resource server audience (default: "
|
|
16
|
+
* Expected resource server audience (default: "komagu-api"). Must not be blank.
|
|
17
17
|
* The pre-rename default is kept so issued tokens stay valid (#290).
|
|
18
18
|
*/
|
|
19
|
-
var audience: String = "
|
|
19
|
+
var audience: String = "komagu-api",
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* JSON Web Key Set (JWKS) endpoint URI. Must use HTTPS unless allowInsecureHttpJwks is true.
|
|
@@ -101,7 +101,7 @@ class KomaguIdentityAutoConfigurationTest {
|
|
|
101
101
|
|
|
102
102
|
val port = server.address.port
|
|
103
103
|
val issuer = "https://auth.komagu.test/realms/komagu"
|
|
104
|
-
val audience = "
|
|
104
|
+
val audience = "komagu-api"
|
|
105
105
|
|
|
106
106
|
try {
|
|
107
107
|
contextRunner
|
|
@@ -255,7 +255,7 @@ class KomaguIdentityAutoConfigurationTest {
|
|
|
255
255
|
}
|
|
256
256
|
server.start()
|
|
257
257
|
|
|
258
|
-
val audience = "
|
|
258
|
+
val audience = "komagu-api"
|
|
259
259
|
|
|
260
260
|
try {
|
|
261
261
|
// Configure ONLY spring.security.oauth2.resourceserver.jwt.issuer-uri
|
package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TestJwtFactory.kt
CHANGED
|
@@ -26,7 +26,7 @@ class TestJwtFactory {
|
|
|
26
26
|
|
|
27
27
|
fun createToken(
|
|
28
28
|
issuer: String = "https://auth.komagu.test/realms/komagu",
|
|
29
|
-
audience: String = "
|
|
29
|
+
audience: String = "komagu-api",
|
|
30
30
|
subject: String? = "alice-100",
|
|
31
31
|
tenantId: String = "tenant-alpha",
|
|
32
32
|
issueTime: Instant = Instant.now(),
|
|
@@ -27,7 +27,7 @@ class TokenValidationIntegrationTest {
|
|
|
27
27
|
|
|
28
28
|
companion object {
|
|
29
29
|
const val EXPECTED_ISSUER = "https://auth.komagu.test/realms/komagu"
|
|
30
|
-
const val EXPECTED_AUDIENCE = "
|
|
30
|
+
const val EXPECTED_AUDIENCE = "komagu-api"
|
|
31
31
|
val jwtFactory = TestJwtFactory()
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
<cfargument name="idValue" type="any" required="true">
|
|
19
19
|
|
|
20
20
|
<cfif isNull(arguments.idValue)>
|
|
21
|
-
<cfthrow type="
|
|
21
|
+
<cfthrow type="Komagu.InvalidId" message="Identifier cannot be null.">
|
|
22
22
|
</cfif>
|
|
23
23
|
|
|
24
24
|
<cfset var s = trim(toString(arguments.idValue))>
|
|
25
25
|
<cfif len(s) EQ 0 OR NOT reFind("^[1-9][0-9]{0,18}$", s)>
|
|
26
|
-
<cfthrow type="
|
|
26
|
+
<cfthrow type="Komagu.InvalidId" message="Identifier must be a positive canonical decimal integer.">
|
|
27
27
|
</cfif>
|
|
28
28
|
|
|
29
29
|
<cfreturn s>
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
<cfargument name="allowList" type="array" required="true">
|
|
60
60
|
|
|
61
61
|
<cfif NOT arrayFind(arguments.allowList, arguments.identifier)>
|
|
62
|
-
<cfthrow type="
|
|
62
|
+
<cfthrow type="Komagu.DisallowedIdentifier" message="Disallowed SQL identifier.">
|
|
63
63
|
</cfif>
|
|
64
64
|
<cfif NOT reFind("^[a-zA-Z_][a-zA-Z0-9_]*$", arguments.identifier)>
|
|
65
|
-
<cfthrow type="
|
|
65
|
+
<cfthrow type="Komagu.InvalidIdentifier" message="Invalid SQL identifier format.">
|
|
66
66
|
</cfif>
|
|
67
67
|
<cfreturn arguments.identifier>
|
|
68
68
|
</cffunction>
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
</cfquery>
|
|
100
100
|
|
|
101
101
|
<cfif qOwnership.recordCount EQ 0>
|
|
102
|
-
<cfthrow type="
|
|
102
|
+
<cfthrow type="Komagu.AccessDenied" message="Record not found or not owned.">
|
|
103
103
|
</cfif>
|
|
104
104
|
|
|
105
105
|
<cfreturn normResId>
|
|
@@ -42,7 +42,7 @@ val safeClause = allowList.buildSortClause(userInputColumn, "DESC")
|
|
|
42
42
|
|
|
43
43
|
The `Komagu.Security.Query.Analyzers` package inspects syntax trees during build time and inside the IDE to flag any string concatenation or string interpolation passed into raw SQL query methods:
|
|
44
44
|
|
|
45
|
-
- **Diagnostic ID:** `
|
|
45
|
+
- **Diagnostic ID:** `KOMAGU001`
|
|
46
46
|
- **Severity:** `Warning` / `Error`
|
|
47
47
|
- **Target APIs:** `FromSqlRaw`, `ExecuteSqlRaw`, `ExecuteSqlRawAsync`, `SqlQueryRaw`, `Query`, `Execute`.
|
|
48
48
|
|
|
@@ -3,5 +3,5 @@ namespace Komagu.Security.Query.Analyzers;
|
|
|
3
3
|
public static class DiagnosticIds
|
|
4
4
|
{
|
|
5
5
|
// Kept after the rename so existing #pragma / .editorconfig suppressions still apply (#290).
|
|
6
|
-
public const string SqlInjectionDiagnosticId = "
|
|
6
|
+
public const string SqlInjectionDiagnosticId = "KOMAGU001";
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<Version>1.0.0-preview.1</Version>
|
|
11
11
|
<Authors>Komagu Contributors</Authors>
|
|
12
12
|
<Company>Komagu Security</Company>
|
|
13
|
-
<Description>Roslyn analyzer rule
|
|
13
|
+
<Description>Roslyn analyzer rule KOMAGU001 detecting SQL injection from string-built queries in C#.</Description>
|
|
14
14
|
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
|
15
15
|
<IsRoslynComponent>true</IsRoslynComponent>
|
|
16
16
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
|
@@ -9,7 +9,7 @@ import io.gitlab.arturbosch.detekt.api.RuleSetProvider
|
|
|
9
9
|
*/
|
|
10
10
|
class KomaguRuleSetProvider : RuleSetProvider {
|
|
11
11
|
// Kept after the rename so existing detekt.yml sections and @Suppress ids still apply (#290).
|
|
12
|
-
override val ruleSetId: String = "
|
|
12
|
+
override val ruleSetId: String = "komagu-security"
|
|
13
13
|
|
|
14
14
|
override fun instance(config: Config): RuleSet {
|
|
15
15
|
return RuleSet(
|
|
@@ -15,18 +15,12 @@ import json
|
|
|
15
15
|
import os
|
|
16
16
|
import sys
|
|
17
17
|
import time
|
|
18
|
-
import warnings
|
|
19
18
|
from typing import Any, Dict, List, Optional, Tuple
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
23
|
-
"""Read KOMAGU_<name
|
|
24
|
-
|
|
25
|
-
if value is None:
|
|
26
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
27
|
-
if value is not None:
|
|
28
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
29
|
-
return default if value is None else value
|
|
22
|
+
"""Read KOMAGU_<name>."""
|
|
23
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
30
24
|
|
|
31
25
|
|
|
32
26
|
|
|
@@ -34,7 +34,7 @@ A Disaster Recovery failover is declared by the Incident Commander / Principal A
|
|
|
34
34
|
### Phase 2: Secondary Cluster Activation (大阪スタンバイ環境起動)
|
|
35
35
|
1. Scale Osaka ECS tasks from 0 (warm standby) to production capacity:
|
|
36
36
|
```bash
|
|
37
|
-
aws ecs update-service --cluster
|
|
37
|
+
aws ecs update-service --cluster komagu-production-standby-dr-cluster \
|
|
38
38
|
--service api-gateway --desired-count 4 --region ap-northeast-3
|
|
39
39
|
```
|
|
40
40
|
2. Rehydrate relational state from S3 Object Lock Compliance WORM bucket:
|
|
@@ -43,7 +43,7 @@ A Disaster Recovery failover is declared by the Incident Commander / Principal A
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
### Phase 3: Route53 Traffic Cutover (DNS切替)
|
|
46
|
-
1. Update DNS failover record to point to Osaka ALB (`osaka-dr-alb.
|
|
46
|
+
1. Update DNS failover record to point to Osaka ALB (`osaka-dr-alb.komagu.internal`).
|
|
47
47
|
2. Verify smoke test health check returns HTTP 200 on all core endpoints.
|
|
48
48
|
|
|
49
49
|
---
|
|
@@ -17,18 +17,12 @@ import os
|
|
|
17
17
|
import secrets
|
|
18
18
|
import sys
|
|
19
19
|
import threading
|
|
20
|
-
import warnings
|
|
21
20
|
from typing import Any, Dict, List, Optional, Tuple
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
25
|
-
"""Read KOMAGU_<name
|
|
26
|
-
|
|
27
|
-
if value is None:
|
|
28
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
29
|
-
if value is not None:
|
|
30
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
31
|
-
return default if value is None else value
|
|
24
|
+
"""Read KOMAGU_<name>."""
|
|
25
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
32
26
|
|
|
33
27
|
|
|
34
28
|
|
|
@@ -15,18 +15,12 @@ import json
|
|
|
15
15
|
import os
|
|
16
16
|
import secrets
|
|
17
17
|
import sys
|
|
18
|
-
import warnings
|
|
19
18
|
from typing import Any, Dict, Optional, Tuple
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
23
|
-
"""Read KOMAGU_<name
|
|
24
|
-
|
|
25
|
-
if value is None:
|
|
26
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
27
|
-
if value is not None:
|
|
28
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
29
|
-
return default if value is None else value
|
|
22
|
+
"""Read KOMAGU_<name>."""
|
|
23
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
30
24
|
|
|
31
25
|
|
|
32
26
|
try:
|
|
@@ -12,18 +12,12 @@ from contextlib import contextmanager
|
|
|
12
12
|
import hmac
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
|
-
import warnings
|
|
16
15
|
from typing import Any, Dict, Iterator, Optional
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
def _env(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
20
|
-
"""Read KOMAGU_<name
|
|
21
|
-
|
|
22
|
-
if value is None:
|
|
23
|
-
value = os.environ.get("KOMAINU_" + name)
|
|
24
|
-
if value is not None:
|
|
25
|
-
warnings.warn(f"KOMAINU_{name} is deprecated, set KOMAGU_{name} instead", DeprecationWarning, stacklevel=2)
|
|
26
|
-
return default if value is None else value
|
|
19
|
+
"""Read KOMAGU_<name>."""
|
|
20
|
+
return os.environ.get("KOMAGU_" + name, default)
|
|
27
21
|
|
|
28
22
|
|
|
29
23
|
# Context variable holding the active tenant_id for the current execution thread/coroutine
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
-- In autocommit connection pools, session state expires immediately after set_current_tenant().
|
|
9
9
|
-- 2. ROLE RESTRICTION: FORCE ROW LEVEL SECURITY enforces policies on table owners
|
|
10
10
|
-- and standard users, but NEVER applies to PostgreSQL superusers or roles with BYPASSRLS.
|
|
11
|
-
-- Applications MUST connect using a non-owner, non-BYPASSRLS application role (
|
|
11
|
+
-- Applications MUST connect using a non-owner, non-BYPASSRLS application role (komagu_app).
|
|
12
12
|
|
|
13
13
|
-- 0. Provision Dedicated Non-Owner Application Role (without hard-coded password)
|
|
14
14
|
-- NOTE: In production environments requiring LOGIN connections, configure passwords
|
|
15
|
-
-- exclusively via deployment orchestration or vault parameters (ALTER ROLE
|
|
15
|
+
-- exclusively via deployment orchestration or vault parameters (ALTER ROLE komagu_app WITH LOGIN PASSWORD ...),
|
|
16
16
|
-- NEVER hard-code credentials in committed migration scripts.
|
|
17
17
|
DO $$
|
|
18
18
|
BEGIN
|
|
19
|
-
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '
|
|
20
|
-
CREATE ROLE
|
|
19
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'komagu_app') THEN
|
|
20
|
+
CREATE ROLE komagu_app WITH NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE NOLOGIN;
|
|
21
21
|
END IF;
|
|
22
22
|
END;
|
|
23
23
|
$$;
|
|
@@ -55,7 +55,7 @@ ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
|
|
55
55
|
DROP POLICY IF EXISTS tenant_org_isolation_policy ON tenant_organizations;
|
|
56
56
|
CREATE POLICY tenant_org_isolation_policy ON tenant_organizations
|
|
57
57
|
FOR ALL
|
|
58
|
-
TO
|
|
58
|
+
TO komagu_app
|
|
59
59
|
USING (
|
|
60
60
|
id = NULLIF(current_setting('app.current_tenant_id', true), '')
|
|
61
61
|
)
|
|
@@ -67,7 +67,7 @@ CREATE POLICY tenant_org_isolation_policy ON tenant_organizations
|
|
|
67
67
|
DROP POLICY IF EXISTS tenant_isolation_policy ON orders;
|
|
68
68
|
CREATE POLICY tenant_isolation_policy ON orders
|
|
69
69
|
FOR ALL
|
|
70
|
-
TO
|
|
70
|
+
TO komagu_app
|
|
71
71
|
USING (
|
|
72
72
|
tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')
|
|
73
73
|
)
|
|
@@ -126,11 +126,11 @@ CREATE TRIGGER trg_orders_tenant_injection
|
|
|
126
126
|
|
|
127
127
|
-- 6. Grant Least-Privilege Access to Application Role & Revoke Public Execution
|
|
128
128
|
REVOKE ALL ON FUNCTION trg_inject_current_tenant() FROM PUBLIC;
|
|
129
|
-
GRANT USAGE ON SCHEMA public TO
|
|
130
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON tenant_organizations TO
|
|
131
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO
|
|
129
|
+
GRANT USAGE ON SCHEMA public TO komagu_app;
|
|
130
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON tenant_organizations TO komagu_app;
|
|
131
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON orders TO komagu_app;
|
|
132
132
|
|
|
133
133
|
REVOKE ALL ON FUNCTION set_current_tenant(VARCHAR) FROM PUBLIC;
|
|
134
134
|
REVOKE ALL ON FUNCTION reset_current_tenant() FROM PUBLIC;
|
|
135
|
-
GRANT EXECUTE ON FUNCTION set_current_tenant(VARCHAR) TO
|
|
136
|
-
GRANT EXECUTE ON FUNCTION reset_current_tenant() TO
|
|
135
|
+
GRANT EXECUTE ON FUNCTION set_current_tenant(VARCHAR) TO komagu_app;
|
|
136
|
+
GRANT EXECUTE ON FUNCTION reset_current_tenant() TO komagu_app;
|
|
@@ -59,10 +59,10 @@ BEGIN
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
-- Grant least-privilege tenant access to application role
|
|
62
|
-
IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '
|
|
63
|
-
EXECUTE format('GRANT USAGE ON SCHEMA %I TO
|
|
64
|
-
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO
|
|
65
|
-
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO
|
|
62
|
+
IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'komagu_app') THEN
|
|
63
|
+
EXECUTE format('GRANT USAGE ON SCHEMA %I TO komagu_app', v_schema_name);
|
|
64
|
+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA %I TO komagu_app', v_schema_name);
|
|
65
|
+
EXECUTE format('ALTER DEFAULT PRIVILEGES IN SCHEMA %I GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO komagu_app', v_schema_name);
|
|
66
66
|
END IF;
|
|
67
67
|
|
|
68
68
|
END;
|
|
@@ -101,11 +101,11 @@ $$ LANGUAGE plpgsql;
|
|
|
101
101
|
REVOKE ALL ON FUNCTION create_tenant_schema(VARCHAR) FROM PUBLIC;
|
|
102
102
|
REVOKE ALL ON FUNCTION switch_tenant_schema(VARCHAR) FROM PUBLIC;
|
|
103
103
|
REVOKE ALL ON TABLE public.tenant_registry FROM PUBLIC;
|
|
104
|
-
-- Note: No SELECT grant on tenant_registry for
|
|
104
|
+
-- Note: No SELECT grant on tenant_registry for komagu_app. Namespace‑only isolation is enforced; cross‑tenant queries must use tenant‑specific schemas.
|
|
105
105
|
DO $$
|
|
106
106
|
BEGIN
|
|
107
|
-
IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '
|
|
108
|
-
GRANT EXECUTE ON FUNCTION switch_tenant_schema(VARCHAR) TO
|
|
107
|
+
IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'komagu_app') THEN
|
|
108
|
+
GRANT EXECUTE ON FUNCTION switch_tenant_schema(VARCHAR) TO komagu_app;
|
|
109
109
|
END IF;
|
|
110
110
|
END $$;
|
|
111
111
|
|
|
@@ -16,7 +16,7 @@ import psycopg2
|
|
|
16
16
|
from psycopg2 import sql
|
|
17
17
|
|
|
18
18
|
# Paths to the SQL scripts relative to the repository root.
|
|
19
|
-
REPO_ROOT = Path(
|
|
19
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
20
20
|
SCHEMA_SQL = REPO_ROOT / "remediate" / "tenancy" / "sql" / "schema_per_tenant.sql"
|
|
21
21
|
RLS_SQL = REPO_ROOT / "remediate" / "tenancy" / "sql" / "rls_isolation.sql"
|
|
22
22
|
|
|
@@ -206,8 +206,8 @@ class TestTenantIsolationAndBYOK(unittest.TestCase):
|
|
|
206
206
|
self.assertIn("REVOKE ALL ON ALL TABLES IN SCHEMA %I FROM PUBLIC", sql_content)
|
|
207
207
|
self.assertIn("REVOKE ALL ON FUNCTION create_tenant_schema(VARCHAR) FROM PUBLIC", sql_content)
|
|
208
208
|
self.assertIn("REVOKE ALL ON FUNCTION switch_tenant_schema(VARCHAR) FROM PUBLIC", sql_content)
|
|
209
|
-
self.assertIn("GRANT USAGE ON SCHEMA %I TO
|
|
210
|
-
self.assertIn("GRANT EXECUTE ON FUNCTION switch_tenant_schema(VARCHAR) TO
|
|
209
|
+
self.assertIn("GRANT USAGE ON SCHEMA %I TO komagu_app", sql_content)
|
|
210
|
+
self.assertIn("GRANT EXECUTE ON FUNCTION switch_tenant_schema(VARCHAR) TO komagu_app", sql_content)
|
|
211
211
|
|
|
212
212
|
def test_11_rls_sql_app_role_and_tenant_organizations_security(self) -> None:
|
|
213
213
|
"""Verifies RLS template defines non-owner app role, protects tenant_organizations, and pins search_path."""
|
|
@@ -215,7 +215,7 @@ class TestTenantIsolationAndBYOK(unittest.TestCase):
|
|
|
215
215
|
with open(rls_file, "r", encoding="utf-8") as f:
|
|
216
216
|
sql_content = f.read()
|
|
217
217
|
|
|
218
|
-
self.assertIn("CREATE ROLE
|
|
218
|
+
self.assertIn("CREATE ROLE komagu_app WITH NOSUPERUSER NOBYPASSRLS", sql_content)
|
|
219
219
|
self.assertIn("NOLOGIN", sql_content)
|
|
220
220
|
self.assertNotIn("replace_with_secure_app_password", sql_content)
|
|
221
221
|
self.assertIn("ALTER TABLE tenant_organizations ENABLE ROW LEVEL SECURITY", sql_content)
|
|
@@ -225,7 +225,7 @@ class TestTenantIsolationAndBYOK(unittest.TestCase):
|
|
|
225
225
|
self.assertIn("TRANSACTION REQUIREMENT", sql_content)
|
|
226
226
|
self.assertIn("BEGIN ... COMMIT", sql_content)
|
|
227
227
|
self.assertIn("REVOKE ALL ON FUNCTION set_current_tenant(VARCHAR) FROM PUBLIC", sql_content)
|
|
228
|
-
self.assertIn("GRANT EXECUTE ON FUNCTION set_current_tenant(VARCHAR) TO
|
|
228
|
+
self.assertIn("GRANT EXECUTE ON FUNCTION set_current_tenant(VARCHAR) TO komagu_app", sql_content)
|
|
229
229
|
|
|
230
230
|
def test_12_rls_cross_tenant_isolation_logic_simulation(self) -> None:
|
|
231
231
|
"""Simulates RLS policy evaluation: verifying cross-tenant records cannot be read."""
|
package/bin/komainu
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Deprecated `bin/komainu` entrypoint: use `bin/komagu` (#290)."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
10
|
-
sys.path.insert(0, str(REPO_ROOT))
|
|
11
|
-
|
|
12
|
-
from operate.cli.main import main # noqa: E402
|
|
13
|
-
|
|
14
|
-
if __name__ == "__main__":
|
|
15
|
-
print("[DEPRECATED] bin/komainu was renamed to bin/komagu.", file=sys.stderr)
|
|
16
|
-
sys.exit(main())
|
package/bin/komainu-legacy.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Deprecated `komainu` / `komainu-guard` npm bin aliases.
|
|
4
|
-
*
|
|
5
|
-
* Prints a rename notice to stderr, then runs the Komagu launcher unchanged.
|
|
6
|
-
* Remove in the next major release (#290).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
'use strict';
|
|
10
|
-
|
|
11
|
-
console.error(
|
|
12
|
-
'\x1b[33m[DEPRECATED]\x1b[0m `komainu` / `komainu-guard` were renamed to `komagu`. ' +
|
|
13
|
-
'Install @hoanghainh/komagu and run `komagu` instead.'
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
require('./komagu.js');
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""KOMAGU_* environment variables with the deprecated KOMAINU_* fallback (#290)."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import unittest
|
|
8
|
-
import warnings
|
|
9
|
-
from unittest.mock import patch
|
|
10
|
-
|
|
11
|
-
from remediate.ekyc.server import tokenization, jpki_verifier
|
|
12
|
-
|
|
13
|
-
MODULES = (tokenization, jpki_verifier)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestLegacyEnvFallback(unittest.TestCase):
|
|
17
|
-
def test_new_name_wins_without_warning(self) -> None:
|
|
18
|
-
env = {"KOMAGU_EKYC_HMAC_KEY": "new", "KOMAINU_EKYC_HMAC_KEY": "old"}
|
|
19
|
-
for mod in MODULES:
|
|
20
|
-
with self.subTest(module=mod.__name__), patch.dict(os.environ, env), warnings.catch_warnings(record=True) as caught:
|
|
21
|
-
warnings.simplefilter("always")
|
|
22
|
-
self.assertEqual(mod._env("EKYC_HMAC_KEY"), "new")
|
|
23
|
-
self.assertEqual(caught, [])
|
|
24
|
-
|
|
25
|
-
def test_legacy_name_is_read_with_deprecation_warning(self) -> None:
|
|
26
|
-
for mod in MODULES:
|
|
27
|
-
with self.subTest(module=mod.__name__), patch.dict(os.environ, {"KOMAINU_EKYC_HMAC_KEY": "old"}, clear=True):
|
|
28
|
-
with self.assertWarnsRegex(DeprecationWarning, "KOMAINU_EKYC_HMAC_KEY is deprecated"):
|
|
29
|
-
self.assertEqual(mod._env("EKYC_HMAC_KEY"), "old")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if __name__ == "__main__":
|
|
33
|
-
unittest.main()
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""KOMAGU_* environment variables with the deprecated KOMAINU_* fallback (#290)."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
import unittest
|
|
8
|
-
import warnings
|
|
9
|
-
from unittest.mock import patch
|
|
10
|
-
|
|
11
|
-
from remediate.identity.python.komagu_identity import oidc_validator
|
|
12
|
-
|
|
13
|
-
MODULES = (oidc_validator,)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestLegacyEnvFallback(unittest.TestCase):
|
|
17
|
-
def test_new_name_wins_without_warning(self) -> None:
|
|
18
|
-
env = {"KOMAGU_ALLOW_DEV_TOKENS": "new", "KOMAINU_ALLOW_DEV_TOKENS": "old"}
|
|
19
|
-
for mod in MODULES:
|
|
20
|
-
with self.subTest(module=mod.__name__), patch.dict(os.environ, env), warnings.catch_warnings(record=True) as caught:
|
|
21
|
-
warnings.simplefilter("always")
|
|
22
|
-
self.assertEqual(mod._env("ALLOW_DEV_TOKENS"), "new")
|
|
23
|
-
self.assertEqual(caught, [])
|
|
24
|
-
|
|
25
|
-
def test_legacy_name_is_read_with_deprecation_warning(self) -> None:
|
|
26
|
-
for mod in MODULES:
|
|
27
|
-
with self.subTest(module=mod.__name__), patch.dict(os.environ, {"KOMAINU_ALLOW_DEV_TOKENS": "old"}, clear=True):
|
|
28
|
-
with self.assertWarnsRegex(DeprecationWarning, "KOMAINU_ALLOW_DEV_TOKENS is deprecated"):
|
|
29
|
-
self.assertEqual(mod._env("ALLOW_DEV_TOKENS"), "old")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if __name__ == "__main__":
|
|
33
|
-
unittest.main()
|