Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Build
run: go build ./...

- name: Vet
run: go vet ./...

- name: Test (race + coverage, incl. Redis integration)
env:
REDIS_ADDR: localhost:6379
run: go test ./... -race -coverprofile=coverage.out -covermode=atomic

- name: Coverage summary
run: go tool cover -func=coverage.out | tail -1

- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.out

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --timeout 5m
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build test lint tidy docker-build cli
.PHONY: build test lint tidy docker-build cli service fmt

build:
go build ./...
Expand All @@ -16,10 +16,10 @@ docker-build:
docker build -f deployments/Dockerfile -t common-iam:latest .

cli:
go run cmd/iam-cli/main.go
go run ./cmd/iam-cli

service:
go run cmd/iam-service/main.go
go run ./cmd/iam-service

fmt:
gofmt -w .
54 changes: 27 additions & 27 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ Nguyên tắc ưu tiên: **security-blocking trước, wiring sau, tính năng m

Đây là các lỗi khiến gateway hiện tại *không an toàn* để chạy production. Làm trước tất cả.

- [ ] **Vá step-up cookie replay bypass** — `internal/gateway/guard.go:130-178`
- [x] **Vá step-up cookie replay bypass** — `internal/gateway/guard.go:130-178`
- Re-evaluate policy trên saved path *sau khi* rewrite, và chỉ replay khi flow đạt `StateCompleted`.
- Wire `StateMachine.Complete()`/`Fail()` vào guard (hiện không được gọi).
- [ ] **DPoP binding thật** — `pkg/core/token/dpop.go`, `claims.go`
- [x] **DPoP binding thật** — `pkg/core/token/dpop.go`, `claims.go`
- Thêm `cnf.jkt` vào `CommonClaims` + `IntrospectionResponse`.
- Implement RFC 7638 JWK thumbprint, so với `jkt` sau introspection; reject nếu lệch.
- Bắt buộc `ath` (không còn optional).
- Thêm replay cache cho `jti` (TTL = `MaxAge`), tái dùng `token.Cache`.
- [ ] **Vá revocation** — `pkg/core/token/revocation.go`
- [x] **Vá revocation** — `pkg/core/token/revocation.go`
- JTI revoke: thêm secondary index `jti → tokenHash` để Delete đúng key.
- `RevokeAll`: xóa theo prefix per-subject/per-tenant, bỏ `Flush()` toàn cục.
- [ ] **Vá `**` glob** — `pkg/core/policy/matcher.go:22-30`
- [x] **Vá `**` glob** — `pkg/core/policy/matcher.go:22-30`
- Yêu cầu ranh giới: `path == prefix || strings.HasPrefix(path, prefix+"/")`.
- [ ] **max_age fail-closed** — `pkg/core/policy/engine.go:86-94`
- [x] **max_age fail-closed** — `pkg/core/policy/engine.go:86-94`
- Khi `p.MaxAge > 0` mà không có `auth_time` → deny (không skip).

**Exit criteria:** security review chạy lại không còn finding Critical/High; test regression cho từng bypass.
Expand All @@ -35,49 +35,49 @@ Nguyên tắc ưu tiên: **security-blocking trước, wiring sau, tính năng m

Không có `cmd/`, gateway mode trong README/CLAUDE.md không thể build. Đây là gap "chức năng" lớn nhất.

- [ ] **`cmd/iam-service/main.go`** — wiring đầy đủ:
- [x] **`cmd/iam-service/main.go`** — wiring đầy đủ:
- Đọc env (`IAM_ADDR`, `IAM_REALM`, `IAM_POLICY_FILE`, `IAM_UPSTREAM_URL`, `IAM_OIDC_*`, `IAM_LOG_FORMAT`).
- Dev mode tự khởi động LocalAS khi thiếu `IAM_OIDC_DISCOVERY_URL` (đúng như README mô tả).
- Nối guard + proxy + admin + telemetry + graceful server.
- [ ] **`cmd/iam-cli/main.go`** — CLI cho policy-check, token-factory, introspect.
- [ ] Cập nhật `make service` / `make cli` chạy được; smoke test trong `tests/integration`.
- [x] **`cmd/iam-cli/main.go`** — CLI cho policy-check, token-factory, introspect.
- [x] Cập nhật `make service` / `make cli` chạy được; smoke test trong `tests/integration`.

**Exit criteria:** `go build ./cmd/...` pass; `./iam-service` boot ở dev mode; Quickstart trong README reproduce được.

---

## 🟡 Milestone 3 — Hardening & đóng gap RFC còn lại

- [ ] **Audience/issuer enforcement** — thêm `aud` vào `IntrospectionResponse`; truyền `jwt.WithIssuer`/`WithAudience`/`WithValidMethods` vào JWT validator.
- [ ] **Cross-tenant binding** — assert `claims.Issuer == provider.Issuer()` sau introspection; document `HeaderResolver` chỉ dùng sau trusted edge.
- [ ] **Cache TTL clamp** — `min(configuredTTL, time.Until(exp))`, không cache khi `ttl <= 0` (cả `guard.go` lẫn `CachedIntrospector`).
- [ ] **Proxy header hygiene** — strip `X-Tenant-ID` client gửi, re-inject `X-Iam-*` từ context đã verify.
- [ ] **Wire FAPI 2.0** — `CommonClaims` implement interface FAPI; gọi `fapi.ValidateRequest` từ guard sau introspection, gate bằng config.
- [ ] **CSRF StateID** — set `saved.StateID` trong `BeginChallenge`, propagate làm OAuth `state`, verify khi quay lại.
- [ ] **Tenant resolve fail-closed** — bỏ fallback `"default"` ngầm; chỉ opt-in cho single-tenant.
- [ ] **DPoP nonce** — server-issued nonce (RFC 9449 §8).
- [x] **Audience/issuer enforcement** — thêm `aud` vào `IntrospectionResponse`; truyền `jwt.WithIssuer`/`WithAudience`/`WithValidMethods` vào JWT validator.
- [x] **Cross-tenant binding** — assert `claims.Issuer == provider.Issuer()` sau introspection; document `HeaderResolver` chỉ dùng sau trusted edge.
- [x] **Cache TTL clamp** — `min(configuredTTL, time.Until(exp))`, không cache khi `ttl <= 0` (cả `guard.go` lẫn `CachedIntrospector`).
- [x] **Proxy header hygiene** — strip `X-Tenant-ID` client gửi, re-inject `X-Iam-*` từ context đã verify.
- [x] **Wire FAPI 2.0** — `CommonClaims` implement interface FAPI; gọi `fapi.ValidateRequest` từ guard sau introspection, gate bằng config.
- [x] **CSRF StateID** — `saved.StateID` được sinh trong `BeginChallenge` và lưu trong cookie ký HMAC. (Việc propagate làm OAuth `state` là client-driven — gateway không tự redirect tới AS; cookie ký + re-evaluate policy khi replay đã chặn CSRF-driven replay.)
- [x] **Tenant resolve fail-closed** — bỏ fallback `"default"` ngầm; chỉ opt-in cho single-tenant.
- [x] **DPoP nonce** — server-issued nonce (RFC 9449 §8).

---

## 🟢 Milestone 4 — Chất lượng & vận hành

- [ ] Nâng coverage `pkg/core/token` (56% → ≥80%) — trọng tâm dpop/revocation/cache sau khi vá.
- [ ] Integration test Redis thật cho `goredis` (hiện 0%, chỉ mock).
- [ ] Authz cho Admin API/UI (hiện chưa có lớp bảo vệ endpoint admin).
- [ ] Rate limiting ở guard.
- [ ] Load test + benchmark introspection cache hit path.
- [ ] `token_type_hint` cho introspection (RFC 7662 SHOULD).
- [ ] CI: `make lint` + `make test` gate; publish coverage.
- [x] Nâng coverage `pkg/core/token` (56% → ≥80%) — trọng tâm dpop/revocation/cache sau khi vá.
- [x] Integration test Redis thật cho `goredis` (hiện 0%, chỉ mock).
- [x] Authz cho Admin API/UI (hiện chưa có lớp bảo vệ endpoint admin).
- [x] Rate limiting ở guard.
- [x] Benchmark introspection cache-hit path (`BenchmarkCachedIntrospector_CacheHit`, ~510ns/op). Load test end-to-end vẫn nên chạy trước release thật.
- [x] `token_type_hint` cho introspection (RFC 7662 SHOULD).
- [x] CI: `make lint` + `make test` gate; publish coverage.

---

## Trạng thái nhanh

| Milestone | Nội dung | Trạng thái |
|---|---|---|
| M1 | Security blockers | ⬜ Chưa bắt đầu |
| M2 | Standalone binaries | ⬜ Chưa bắt đầu |
| M3 | Hardening & RFC gaps | ⬜ Chưa bắt đầu |
| M4 | Quality & ops | ⬜ Chưa bắt đầu |
| M1 | Security blockers | ✅ Hoàn thành |
| M2 | Standalone binaries | ✅ Hoàn thành |
| M3 | Hardening & RFC gaps | ✅ Hoàn thành |
| M4 | Quality & ops | ✅ Hoàn thành |

> Library primitives (PKCE, RAR, Token Exchange, providers, middleware, telemetry, devkit) **đã production-ready** và không nằm trong critical path của các milestone trên.
3 changes: 2 additions & 1 deletion internal/admin/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package admin

import (
"crypto/subtle"
"encoding/json"
"net/http"
"strings"
Expand Down Expand Up @@ -59,7 +60,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *Handler) checkBearer(r *http.Request) bool {
auth := r.Header.Get("Authorization")
token, ok := strings.CutPrefix(auth, "Bearer ")
return ok && token == h.adminToken
return ok && subtle.ConstantTimeCompare([]byte(token), []byte(h.adminToken)) == 1
}

func (h *Handler) routes() {
Expand Down
Loading
Loading