237 lines
26 KiB
Markdown
237 lines
26 KiB
Markdown
# YAML 한 줄이 Redis 명령을 거절하기까지: Policy Loader·Catalog·Guard
|
|
|
|
> **Redis 코드 상세 시리즈 07/20** · [전체 지도](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-backend-policy-boundary.md) · 이전: [Redis 연결을 여섯 lane으로 나눈 이유: Pool과 RuntimeOwner 생명주기](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-connection-lanes-lifecycle.md) · 다음: [Raw key와 영구 쓰기를 막는 코드: Namespace·Hash Slot·TTL](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-keyspace-expiration.md)
|
|
|
|
## 이 글이 답하는 코드 질문
|
|
|
|
Redis 명령 하나가 애플리케이션 코드에서 Lettuce 호출로 넘어가기 전에 무엇을 검사합니까?
|
|
|
|
이 질문은 다음 세 경계를 나눠 읽어야 답할 수 있습니다.
|
|
|
|
- YAML은 조직이 명령을 어떻게 분류했는지 기록합니다.
|
|
- catalog는 분류되지 않은 명령을 기본 거절합니다.
|
|
- guard는 서버 능력, permit, namespace, slot, budget, timeout을 순서대로 검사합니다.
|
|
|
|
기준은 source HEAD `3b5aee50e33c44c02d08c94bb39ad34814482010`, 2026-08-13입니다.
|
|
|
|
정적 조사 결과 정책 파일에는 명령과 subcommand를 합쳐 314개 항목이 있습니다. `WAIT` 항목은 없습니다. 따라서 현재 `WAIT`는 허용 명령이 아니라 catalog 조회에서 거절되는 default-deny 대상입니다.
|
|
|
|
## 먼저 보는 클래스·리소스 지도
|
|
|
|
| 진입점 | 입력 | 출력 | 다음 호출 |
|
|
|---|---|---|---|
|
|
| [redis-command-policy.yml](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/resources/redis-sdk/redis-command-policy.yml:1) | 명령별 scalar 필드 | 조직 정책 314개 | `RedisCommandPolicyLoader` |
|
|
| [RedisCommandPolicyLoader.loadDefault](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoader.java:59) | classpath YAML | `Map<CommandId, RedisCommandPolicy>` | `RedisCommandCatalog` |
|
|
| [RedisCommandCatalog.require](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandCatalog.java:56) | `CommandId` | 분류된 policy | `CommandPolicyGuard` |
|
|
| [CommandRequest](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandRequest.java:34) | key, 크기, permit, budget, block, 지연된 invocation | 실행 전 요청 | executor |
|
|
| [CommandPolicyGuard.validate](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:89) | `CommandRequest` | `CommandAdmission` | sync/reactive/queueing executor |
|
|
| [ConfiguredRedisPermitVerifier](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/ConfiguredRedisPermitVerifier.java:22) | permit와 요구 policy | 통과 또는 거절 | guard/context |
|
|
| [RedisCommandDescriptor](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisCommandDescriptor.java:12) | policy에서 파생된 값 | 실행 불변식 | lane·translator |
|
|
|
|
`CommandRequest.invocation`은 이미 시작한 future가 아니라 `Supplier<CompletionStage<R>>`입니다. [invocation field 선언](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandRequest.java:34) 덕분에 guard가 끝나기 전에는 driver call이 시작되지 않습니다.
|
|
|
|
## 객체 생성 시점과 request-time을 구분합니다
|
|
|
|
### 객체 생성 시점
|
|
|
|
의도된 조립 순서는 다음과 같습니다.
|
|
|
|
1. `RedisCommandPolicyLoader`가 `/redis-sdk/redis-command-policy.yml`을 읽습니다.
|
|
2. loader가 각 block을 `RedisCommandPolicy`로 바꿉니다.
|
|
3. `RedisCommandCatalog`가 immutable map을 소유합니다.
|
|
4. deployment 설정으로 `ConfiguredRedisPolicyAuthority`와 verifier를 만듭니다.
|
|
5. probed `RedisCapabilities`, `RedisNamespace`, renderer, slot calculator로 guard를 만듭니다.
|
|
6. guard와 translator를 sync/reactive/queueing executor에 주입합니다.
|
|
|
|
그러나 이 순서가 production Spring bean으로 완성됐다고 볼 근거는 없습니다. [RedisSdkAutoConfiguration](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfiguration.java:226)은 runtime client와 owner를 만들지만 catalog, authority, verifier, guard, executor bean은 만들지 않습니다.
|
|
|
|
### request-time
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant O as Typed/Advanced operation
|
|
participant R as CommandRequest
|
|
participant G as CommandPolicyGuard
|
|
participant C as RedisCommandCatalog
|
|
participant E as Executor
|
|
participant L as Lettuce gateway
|
|
O->>R: key·size·optional permit/budget·invocation 구성
|
|
E->>G: validate(request)
|
|
G->>C: require(commandId)
|
|
C-->>G: policy 또는 default-deny
|
|
G->>G: reachability→capability→permit→namespace→slot→budget(if present)→timeout
|
|
G-->>E: CommandAdmission
|
|
E->>L: invocation.get()
|
|
```
|
|
|
|
여기서 관측한 reply 크기와 예외 번역은 `validate` 안에 있지 않습니다. guard의 주석은 전체 pipeline을 요약하지만, 실제 `validate`는 admission까지 담당합니다. [requireRequestBudget](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:207)이 비교하는 값은 request byte와 request builder가 미리 선언한 `expectedReplyBytes`입니다. typed decoder path에서 서버가 돌려준 byte·element 수를 `OperationBudget`과 비교하려면 해당 decoder가 [RedisOperationContext.requireReplyWithinBudget](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationContext.java:360)을 명시적으로 호출해야 합니다.
|
|
|
|
그 호출은 MGET, bounded range, collection page 같은 일부 typed decoder에는 있지만 모든 경로에 있지는 않습니다. 기본 [GET request](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:43)은 `expectedReplyBytes`가 0이고 [decode](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/ValueOperationRequests.java:295)에도 관측 reply 검사가 없습니다. script, function, raw, admin은 budget을 request에 붙이지만 결과 decoder 앞에서 관측 크기를 검사하지 않습니다. extension은 더 나뉩니다. [policy name이 있으면 collection budget을 붙이고 null이면 budget을 비우며](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/extensions/ExtensionCommandRunner.java:85), 어느 분기도 관측 reply 크기를 검사하지 않습니다.
|
|
|
|
batch는 이 typed helper를 쓰지 않는 별도 경로입니다. [preflight](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/BatchExecution.java:90)에서는 item의 declared `expectedReplyBytes` 합계를 검사하고, 응답 뒤에는 [decoded result shape의 근사치를 누적](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/BatchExecution.java:180)합니다. 이 값은 exact wire bytes가 아닙니다. 따라서 admission을 통과했다는 사실만으로 실제 reply byte ceiling까지 집행됐다고 말할 수 없습니다.
|
|
|
|
## YAML parser가 fail-closed인 방식
|
|
|
|
loader는 범용 YAML parser를 사용하지 않습니다. 허용하는 문법은 `commands:` root 하나, 명령 block, scalar field뿐입니다.
|
|
|
|
[readBlocks](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoader.java:84)는 다음 입력을 거절합니다.
|
|
|
|
- tab이 들어간 문서
|
|
- 두 번째 root 또는 `commands:`가 아닌 root
|
|
- root보다 먼저 나온 command block
|
|
- 0·2·4칸 외 indentation
|
|
- 중복 command
|
|
- 알 수 없는 field
|
|
- 값이 비어 있는 field
|
|
- 중복 field
|
|
|
|
허용 field 집합은 [FIELDS](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoader.java:39)에 고정되어 있습니다. `risk`와 `support`는 필수입니다. boolean은 정확히 `true` 또는 `false`여야 합니다.
|
|
|
|
### 기본값도 정책입니다
|
|
|
|
[toPolicy](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoader.java:149)는 생략한 값을 다음처럼 채웁니다.
|
|
|
|
- `minimum-version`: `7.2`
|
|
- `read-only`: `false`
|
|
- `blocking`: `false`
|
|
- `retry-safe`: `read-only` 값
|
|
- `may-be-ambiguous`: `!read-only`
|
|
- `key-spec`: `1 1 1`
|
|
- `access`: support class에서 파생
|
|
- `timeout-profile`: risk와 blocking에서 파생
|
|
|
|
`key-spec`은 `none`, `movable`, 또는 `<first> <last> <step>`만 읽습니다. [keySpec parser](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoader.java:201)가 다른 표기를 거절합니다.
|
|
|
|
## R1~R4와 support class는 다른 축입니다
|
|
|
|
[RedisRiskLevel](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisRiskLevel.java:4)은 비용과 위험을 분류합니다.
|
|
|
|
| risk | 코드상 의미 |
|
|
|---|---|
|
|
| `R1` | bounded single-key ordinary command |
|
|
| `R2` | O(N), 큰 reply, blocking, multi-key, 큰 payload 등; permit와 budget 필요 |
|
|
| `R3` | server·client·ACL·topology 작업; application path 거절 |
|
|
| `R4` | destructive; SDK 전체 차단 |
|
|
|
|
[CommandSupport](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/CommandSupport.java:4)은 어떤 surface로 노출하는지 정합니다.
|
|
|
|
- `TYPED`
|
|
- `ADVANCED_TYPED`
|
|
- `RAW_ONLY`
|
|
- `ADMIN_ONLY`
|
|
- `VERSION_GATED`
|
|
- `BLOCKED`
|
|
|
|
두 축의 조합은 자유롭지 않습니다. [RedisCommandDescriptor constructor](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisCommandDescriptor.java:25)는 `R4`가 `BLOCKED`가 아니거나 `R3`가 `ADMIN_ONLY`/`BLOCKED`가 아니면 실패합니다. ambiguous write를 retry-safe로 표시하는 조합도 거절합니다.
|
|
|
|
## Guard의 실제 검사 순서
|
|
|
|
[validate](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:89)의 순서는 다음과 같습니다.
|
|
|
|
1. catalog에서 policy를 찾습니다.
|
|
2. `BLOCKED`, `NONE`, application에서 도달할 수 없는 risk를 거절합니다.
|
|
3. probed server version이 `minimumVersion`을 만족하는지 봅니다.
|
|
4. R2이면 permit와 budget을 요구합니다.
|
|
5. 모든 key가 process namespace에 속하는지 확인하고 render합니다.
|
|
6. key의 slot을 계산하고 Cluster에서 여러 slot이면 거절합니다.
|
|
7. request byte와 예상 reply byte가 budget 이내인지 확인합니다.
|
|
8. effective timeout을 계산합니다.
|
|
9. descriptor로 connection lane을 정해 `CommandAdmission`을 반환합니다.
|
|
|
|
이 순서에서 실패하면 invocation supplier는 평가되지 않습니다. 즉 namespace 위반이나 budget 초과는 Redis 서버 오류가 아니라 전송 전 SDK 거절입니다.
|
|
|
|
## Permit은 marker interface가 아닙니다
|
|
|
|
R2 요청에 `AdvancedOperationPermit` 구현체를 넣었다고 통과하지 않습니다. [ConfiguredRedisPermitVerifier.check](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/ConfiguredRedisPermitVerifier.java:71)는 네 가지를 확인합니다.
|
|
|
|
1. concrete granted type인가
|
|
2. 현재 authority의 issuer id인가
|
|
3. HMAC signature가 맞는가
|
|
4. command가 요구한 policy name과 같은가
|
|
|
|
여러 key를 건드리는 R2 요청은 advanced permit과 별개로 multi-key permit이 필요합니다. [별도 검사](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:145)는 한 permit이 비싼 연산 승인과 fan-out 승인을 동시에 뜻하지 않게 합니다.
|
|
|
|
## 정상·거절·timeout 분기
|
|
|
|
### 정상 분기
|
|
|
|
`GET`처럼 catalog의 R1/TYPED 명령은 server version과 namespace를 통과하면 기본 `FAST` timeout 500ms와 `REGULAR` lane을 받습니다.
|
|
|
|
R2 명령은 올바른 policy로 발급된 permit, 필요한 multi-key permit, 양수 budget을 갖춰야 admission을 받습니다. non-blocking 명령과 server block을 선언하지 않은 optional-blocking 명령에서는 budget timeout이 policy 기본 timeout을 덮습니다.
|
|
|
|
### 거절 분기
|
|
|
|
- catalog에 없는 명령: `RedisCommandRejectedException`, not sent
|
|
- `BLOCKED`/R4: SDK 전체 거절
|
|
- server version 미달: `RedisCapabilityUnavailableException`
|
|
- permit 없음·위조·다른 policy: `RedisCommandRejectedException`
|
|
- namespace 이탈: `RedisCommandRejectedException`
|
|
- Cluster cross-slot: `RedisCrossSlotException`
|
|
- request/예상 reply budget 초과: `RedisCommandRejectedException`
|
|
- bounded block 누락·0·음수·상한 초과: `RedisCommandRejectedException`
|
|
|
|
blocking 명령이 bounded server block을 선언한 경우에는 budget timeout을 쓰지 않습니다. [effectiveTimeout](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:231)은 block 상한을 검사한 뒤 `serverBlock + BLOCKING_MARGIN(2초)`를 반환합니다. `BLPOP`처럼 block이 필수인 명령은 선언이 없으면 거절하고, `XREAD`처럼 optional인 명령은 block을 생략했을 때만 budget 또는 profile timeout으로 돌아갑니다.
|
|
|
|
### `WAIT`는 현재 사용할 수 없습니다
|
|
|
|
정책 YAML의 314개 block을 정적으로 세었지만 `WAIT` block은 찾지 못했습니다. catalog는 unknown command에 permissive fallback을 두지 않습니다. [default-deny require](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandCatalog.java:56) 때문에 `WAIT`를 typed, raw, semantic surface에서 실행할 수 있다고 읽으면 안 됩니다.
|
|
|
|
기존 [operations.md](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/docs/redis/operations.md:62)의 `WAIT` 설명은 실행 가능한 현행 surface의 근거가 아닙니다.
|
|
|
|
## 테스트가 고정하는 계약
|
|
|
|
policy loader 테스트는 계약마다 시작점을 나눠 읽을 수 있습니다.
|
|
|
|
- [`GET`의 R1과 `KEYS`의 `BLOCKED/NONE`](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:21)
|
|
- [access·timeout·retry·ambiguity 파생](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:52)
|
|
- [version-gated minimum version 보존](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:80)
|
|
- [모든 R4 command 차단](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:94)
|
|
- [advanced command의 required policy name](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:113)
|
|
- [unknown field·enum·duplicate·tab·잘못된 root 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:124)
|
|
- [unclassified command default-deny](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:153)
|
|
- [deprecated command name 차단](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:163)
|
|
- [arbitrary `EVAL` 차단과 registered `EVALSHA` 분리](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandPolicyLoaderTest.java:187)
|
|
|
|
guard 테스트도 한 링크에 여러 사례를 묶지 않습니다.
|
|
|
|
- [ordinary command의 lane과 timeout](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:47)
|
|
- [R2 permit·budget 필수](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:56)
|
|
- [caller 구현 permit 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:63)
|
|
- [advanced permit만 있는 multi-key 요청 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:75)
|
|
- [두 permit을 가진 multi-key advanced 요청 허용](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:94)
|
|
- [다른 policy용 permit 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:135)
|
|
- [blocked command 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:149)
|
|
- [foreign namespace 전송 전 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:158)
|
|
- [Cluster cross-slot 전송 전 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:168)
|
|
- [standalone의 slot 불일치 허용](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:194)
|
|
- [request budget 초과 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:207)
|
|
- [blocking server block 상한과 2초 margin](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:233)
|
|
- [server version 미달 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:256)
|
|
|
|
permit provenance는 [authority가 발급한 permit 허용](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:24), [caller 구현체 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:38), [다른 policy permit 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:53), [다른 authority permit 거절](/home/donghyeon/workspace/desktop-server-git/clean-architecture-backend-template/src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:62)로 각각 고정됩니다.
|
|
|
|
이 테스트들은 이번 문서 작업에서 실행하지 않았습니다. source를 정적으로 조사했습니다. 공유 검증 기록에 따르면 기본 module test는 이전 root 세션에서 성공했지만, 이것을 이번 실행 결과로 표현하지 않습니다.
|
|
|
|
## 현재 구현 공백과 잘못 읽기 쉬운 지점
|
|
|
|
1. 314개 policy와 guard 구현은 존재하지만 production bean 조립은 확인되지 않습니다.
|
|
2. aggregate facade와 executor까지 조립되지 않았으므로 “애플리케이션의 모든 Redis 명령이 현재 이 guard를 지난다”고 단정할 수 없습니다.
|
|
3. admission guard는 request 크기와 예상 reply 크기만 budget과 비교합니다. typed decoder의 actual-size 집행은 일부 경로에만 있습니다. 기본 GET, script, function, raw, admin에는 그 호출이 없고, extension은 policy name이 있을 때만 budget을 갖지만 어느 분기도 관측 reply를 검사하지 않습니다. batch는 decoded shape를 별도로 근사 측정하므로 exact wire-byte 집행이 아닙니다.
|
|
4. permit은 Redis ACL을 넓히지 않습니다. process 내부 provenance 증명이며 실제 보안 경계는 계정 ACL입니다.
|
|
5. `WAIT`는 policy에 없으므로 현재 default-deny입니다.
|
|
6. server metadata drift gate용 코드와 테스트가 있어도 이 조사에서는 real-server metadata 비교를 실행하지 않았습니다.
|
|
|
|
다음에 source를 열 때는 policy YAML, loader, catalog, guard, `CommandRequest`, 각 executor 순으로 보면 됩니다.
|
|
|
|
## 시리즈의 관련 문서
|
|
|
|
관련 범위는 keyspace·expiration, typed operations, advanced surfaces, execution failure certainty입니다.
|
|
|
|
## 시리즈에서 이어 읽기
|
|
|
|
- 이전 글: [Redis 연결을 여섯 lane으로 나눈 이유: Pool과 RuntimeOwner 생명주기](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-connection-lanes-lifecycle.md)
|
|
- 다음 글: [Raw key와 영구 쓰기를 막는 코드: Namespace·Hash Slot·TTL](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-keyspace-expiration.md)
|
|
- 전체 흐름: [Redis를 범용 클라이언트가 아니라 정책 경계로 다루기](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-backend-policy-boundary.md)
|
|
- 운영 흐름: [Redis를 켠다는 말의 운영적 의미: 단일 활성화 스위치에서 Sentinel 쓰기 손실 검증까지](/home/donghyeon/workspace/ai-tool/document-haness/.run/redis/redis-platform-sre-operations.md)
|
|
|