{"id":47803978,"url":"https://github.com/o3co/grpc.authz","last_synced_at":"2026-04-03T17:23:12.683Z","repository":{"id":343141689,"uuid":"1173333750","full_name":"o3co/grpc.authz","owner":"o3co","description":"Declare gRPC authorization policy in .proto method options, enforce via interceptors. Built-in adapters for OPA, Cedar, and local static rules.","archived":false,"fork":false,"pushed_at":"2026-04-02T10:30:15.000Z","size":189,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-04-03T00:55:17.176Z","etag":null,"topics":["abac","authorization","cedar","go","grpc","interceptor","middleware","opa","policy","protobuf"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/o3co/grpc.authz/policy_verification","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/o3co.png","metadata":{"files":{"readme":"README.ja.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-05T08:53:29.000Z","updated_at":"2026-04-02T10:30:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/o3co/grpc.authz","commit_stats":null,"previous_names":["o3co/authorization.go","o3co/grpc.authorization.go"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/o3co/grpc.authz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o3co%2Fgrpc.authz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o3co%2Fgrpc.authz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o3co%2Fgrpc.authz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o3co%2Fgrpc.authz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/o3co","download_url":"https://codeload.github.com/o3co/grpc.authz/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o3co%2Fgrpc.authz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31365749,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:13:05.644Z","status":"ssl_error","status_checked_at":"2026-04-03T17:13:04.413Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abac","authorization","cedar","go","grpc","interceptor","middleware","opa","policy","protobuf"],"created_at":"2026-04-03T17:23:11.937Z","updated_at":"2026-04-03T17:23:12.670Z","avatar_url":"https://github.com/o3co.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# grpc.authz\n\n[![CI](https://github.com/o3co/grpc.authz/actions/workflows/ci.yml/badge.svg)](https://github.com/o3co/grpc.authz/actions/workflows/ci.yml)\n[![Go Reference (policy_verification)](https://pkg.go.dev/badge/github.com/o3co/grpc.authz/policy_verification.svg)](https://pkg.go.dev/github.com/o3co/grpc.authz/policy_verification)\n[![Go Reference (protobuf_policy_option)](https://pkg.go.dev/badge/github.com/o3co/grpc.authz/protobuf_policy_option.svg)](https://pkg.go.dev/github.com/o3co/grpc.authz/protobuf_policy_option)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n\n[English](README.md)\n\n`grpc.authz` は Go 向けの gRPC 認可ミドルウェアライブラリ。`.proto` のメソッドオプションにアクセスポリシー（リソース + アクション）を宣言し、インターセプターで適用。`protobuf_policy_option`（ポリシー宣言・解決）と `policy_verification`（認可バックエンドへの適用）の2つの独立モジュールで構成。\n\n## なぜ必要か\n\n認可ポリシーがハンドラのコードに散在すると、API コントラクトから乖離する。レビューで見落とされ、リファクタで壊れ、RPC ごとにボイラープレートが必要になる。ポリシーを `.proto` のメソッド定義に同居させることで、API 設計と同じ場所でルールが見え、コードレビューで監査でき、実行時に自動適用される。\n\n## 動作の仕組み\n\n```text\ngRPC リクエスト\n     │\n     ▼\n┌─────────────────────────────────────────┐\n│  protobuf_policy_option.Interceptor     │  .proto の (o3.policy) オプションを読み取り、\n│                                         │  field_mappings でリクエストから値を解決し、\n│                                         │  Policy{Resource, Action} を ctx に注入\n└──────────────────┬──────────────────────┘\n                   │ ctx がポリシーを運搬\n                   ▼\n┌─────────────────────────────────────────┐\n│  policy_verification.Interceptor        │  ctx からポリシーを読み取り、\n│                                         │  認可サーバーに POST /verify し、\n│                                         │  HTTP ステータスを gRPC ステータスに変換\n└──────────────────┬──────────────────────┘\n                   │\n                   ▼\n             handler（あなたのコード）\n```\n\n2 つのモジュールは独立した Go モジュールで、責務を意図的に分離しています：\n\n| モジュール | 責務 |\n| --- | --- |\n| `protobuf_policy_option` | proto レジストリから `(o3.policy)` メソッドオプションを読み取り、`\u003cplaceholder\u003e` トークンをリクエストフィールドで解決し、結果を `context.Context` に格納 |\n| `policy_verification` | コンテキストから解決済みポリシーを読み取り、外部認可サーバーに `POST /verify` で問い合わせ、HTTP レスポンスを適切な gRPC ステータスコードに変換 |\n\n分離することで、ポリシー宣言レイヤーに触れずに認可バックエンド（REST の代わりに gRPC など）を差し替え可能で、各関心事を独立してテストできます。\n\n## インターセプタチェーン\n\n2 つのインターセプタは必ずこの順序でチェーンしてください：\n\n```text\n[1] protobuf_policy_option.Interceptor   →  ポリシーを ctx に解決\n[2] policy_verification.Interceptor      →  ctx からポリシーを読み取り検証\n```\n\n順序が逆、または `protobuf_policy_option.Interceptor` が未登録の場合、`policy_verification.Interceptor` は全リクエストで `codes.Internal`（`protobuf_policy_option.Interceptor is not registered in the interceptor chain`）を返します。\n\n```go\nimport (\n    \"log/slog\"\n    \"time\"\n    policyoption       \"github.com/o3co/grpc.authz/protobuf_policy_option\"\n    policyverification \"github.com/o3co/grpc.authz/policy_verification\"\n    pvendpoint         \"github.com/o3co/grpc.authz/policy_verification/endpoint\"\n)\n\nverifier, err := pvendpoint.NewRESTEndpoint(\n    \"http://auth-service/\",\n    pvendpoint.WithTimeout(5 * time.Second),\n    pvendpoint.WithLogLevel(slog.LevelError),\n)\nif err != nil { /* handle */ }\n\ngrpc.NewServer(\n    grpc.ChainUnaryInterceptor(\n        policyoption.Interceptor(\n            policyoption.WithLogLevel(slog.LevelError),\n        ),\n        policyverification.Interceptor(verifier,\n            policyverification.WithLogLevel(slog.LevelError),\n        ),\n    ),\n    grpc.ChainStreamInterceptor(\n        policyoption.StreamInterceptor(\n            policyoption.WithLogLevel(slog.LevelError),\n        ),\n        policyverification.StreamInterceptor(verifier,\n            policyverification.WithLogLevel(slog.LevelError),\n        ),\n    ),\n)\n```\n\n## Proto オプションリファレンス\n\n認可が必要なメソッドに `(o3.policy)` オプションを宣言します：\n\n```proto\nsyntax = \"proto3\";\n\nimport \"policy.proto\";  // (o3.policy) 拡張を提供\n\nservice PostService {\n\n  // 静的リソース — フィールド抽出不要\n  rpc ListPosts(ListPostsRequest) returns (ListPostsResponse) {\n    option (o3.policy) = {\n      resource: \"posts\"   // /verify に送信されるリソース識別子\n      action: \"list\"      // /verify に送信されるアクション文字列\n    };\n  }\n\n  // 動的リソース — リクエストフィールドからプレースホルダーを解決\n  rpc GetPost(GetPostRequest) returns (GetPostResponse) {\n    option (o3.policy) = {\n      resource: \"posts/\u003cid\u003e\"          // \u003cid\u003e は実行時に置換\n      action: \"read\"\n      field_mappings: [\n        { placeholder: \"id\", request_field: \"id\" }\n        // placeholder: リソーステンプレート内の名前（山括弧なし）\n        // request_field: リクエストメッセージの proto フィールド名\n      ]\n    };\n  }\n}\n```\n\n`field_mappings` はスカラー型の proto フィールドをサポートします：`string`、`bytes`、`int32/64`、`uint32/64`、`bool`。`repeated` フィールド、`map` フィールド、ネストされたメッセージはサポートしていません。\n\n## クイックスタート（Unary）\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"log/slog\"\n    \"net\"\n    \"time\"\n\n    \"google.golang.org/grpc\"\n\n    policyoption       \"github.com/o3co/grpc.authz/protobuf_policy_option\"\n    policyverification \"github.com/o3co/grpc.authz/policy_verification\"\n    pvendpoint         \"github.com/o3co/grpc.authz/policy_verification/endpoint\"\n\n    // 生成された proto パッケージ\n    postv1 \"example.com/myapp/gen/post/v1\"\n)\n\nfunc main() {\n    verifier, err := pvendpoint.NewRESTEndpoint(\n        \"http://auth-service/\",\n        pvendpoint.WithTimeout(5 * time.Second),\n    )\n    if err != nil {\n        log.Fatalf(\"failed to create verifier: %v\", err)\n    }\n\n    srv := grpc.NewServer(\n        grpc.ChainUnaryInterceptor(\n            policyoption.Interceptor(\n                policyoption.WithLogLevel(slog.LevelWarn),\n            ),\n            policyverification.Interceptor(verifier),\n        ),\n    )\n\n    postv1.RegisterPostServiceServer(srv, \u0026postServiceServer{})\n\n    lis, err := net.Listen(\"tcp\", \":50051\")\n    if err != nil {\n        log.Fatalf(\"listen: %v\", err)\n    }\n    log.Fatal(srv.Serve(lis))\n}\n```\n\n`(o3.policy)` オプションのないメソッドは、認可チェックなしでそのまま通過します。\n\n## ストリーミング RPC\n\n`Interceptor` と同じチェーン順序で `StreamInterceptor` を使用します：\n\n```go\ngrpc.ChainStreamInterceptor(\n    policyoption.StreamInterceptor(),\n    policyverification.StreamInterceptor(verifier),\n)\n```\n\nストリーミング RPC では、認可はストリーム開始時だけでなく**毎回の `RecvMsg` 呼び出し時**にチェックされます。ストリーム中にトークンが失効した場合、次のメッセージで拒否されます。\n\n**ストリーミング RPC では `field_mappings` はサポートされていません。** ストリーム確立時にリクエストメッセージが利用できないため、プレースホルダー付きのリソーステンプレートは解決できません。ストリーミングメソッドの proto オプションに `field_mappings` が含まれている場合、インターセプタは `codes.Internal` を返します。代わりに静的リソース文字列を使用してください：\n\n```proto\nrpc WatchPosts(WatchPostsRequest) returns (stream Post) {\n  option (o3.policy) = {\n    resource: \"posts\"   // 静的 — field_mappings なし\n    action: \"watch\"\n  };\n}\n```\n\n## サービスのテスト\n\n`endpointtest` パッケージは、テスト用のモック `VerifierEndpoint` 実装を提供します。テストファイルからのみインポートしてください。\n\n```go\nimport \"github.com/o3co/grpc.authz/policy_verification/endpointtest\"\n```\n\n```go\n// 常に許可 — 正常パスのテスト用\nverifier := endpointtest.Allow()\n\n// 常に拒否（codes.PermissionDenied） — アクセス拒否動作のテスト用\nverifier := endpointtest.Deny()\n\n// カスタムロジック — テストでリソースとアクションを検査\nverifier := endpointtest.Func(func(ctx context.Context, resource, action string) error {\n    if resource == \"posts/123\" \u0026\u0026 action == \"read\" {\n        return nil\n    }\n    return status.Error(codes.PermissionDenied, \"access denied\")\n})\n```\n\nテスト用コンテキストを構築するヘルパー関数：\n\n```go\n// gRPC incoming metadata に \"Authorization: Bearer \u003ctoken\u003e\" を注入\nctx = endpointtest.CtxWithBearerToken(ctx, \"my-token\")\n\n// 決定論的なテストアサーションのために既知の x-request-id を注入\nctx = endpointtest.CtxWithRequestID(ctx, \"test-request-id\")\n```\n\nエラーの gRPC ステータスコードをアサート：\n\n```go\n// err が codes.PermissionDenied であることをアサート\nendpointtest.AssertGRPCCode(t, err, codes.PermissionDenied)\n```\n\n## 認可サーバーコントラクト\n\n`policy_verification` モジュールは RPC 呼び出しごとに 1 つの HTTP リクエストを送信します（ストリーミングでは `RecvMsg` ごと）：\n\n```http\nPOST /verify\nContent-Type: application/json\nAuthorization: Bearer \u003cgRPC incoming metadata から転送されたトークン\u003e\nx-request-id: 20260318120530_a1b2c3d4e5f6...\n\n{\"resource\": \"posts/123\", \"action\": \"read\"}\n```\n\nヘッダー転送ルール：\n\n- `Authorization`：必須。gRPC `authorization` メタデータからそのまま転送。不在の場合、インターセプタはリクエスト送信前に `codes.Unauthenticated` を返します。\n- `x-request-id`：gRPC メタデータに存在する場合に転送。不在の場合、`YYYYMMDDHHmmss_\u003c32文字の16進数\u003e` 形式で新しい ID が生成されます。\n\nレスポンス → gRPC ステータスコードのマッピング：\n\n| HTTP レスポンス | gRPC ステータスコード |\n| --- | --- |\n| `2xx` | `codes.OK`（リクエスト続行） |\n| `401` | `codes.Unauthenticated` |\n| `403` | `codes.PermissionDenied` |\n| その他 | `codes.Internal` |\n\n認可サーバーのレスポンスボディは gRPC クライアントに転送されません（内部情報の漏洩防止のため）。デバッグ用にエラーレベルでログ出力されます（最大 1 KB）。\n\n## 代替バックエンド\n\n`policy_verification` モジュールは `VerifierEndpoint` インターフェースを実装する任意の認可バックエンドで動作します。組み込みアダプタ：\n\n### 静的ルール（外部サービス不要）\n\n```go\nimport pvendpoint \"github.com/o3co/grpc.authz/policy_verification/endpoint\"\n\nverifier := pvendpoint.NewStaticEndpoint([]pvendpoint.StaticRule{\n    {Resource: \"posts\", Action: \"list\"},\n    {Resource: \"posts/*\", Action: \"read\"},      // プレフィックスワイルドカード\n    {Resource: \"users\", Action: \"*\"},            // 任意のアクション\n})\n```\n\nルールはローカルで評価されます。外部サービスは不要です。完全一致、`*`（全マッチ）、プレフィックスワイルドカード（`posts/*`）をサポートします。開発環境、シンプルなデプロイ、または OPA や Cedar 導入前の出発点として便利です。\n\n### Open Policy Agent (OPA)\n\n```go\nimport (\n    \"time\"\n    pvendpoint \"github.com/o3co/grpc.authz/policy_verification/endpoint\"\n)\n\nverifier, err := pvendpoint.NewOPAEndpoint(\n    \"http://opa:8181\",       // OPA サーバー URL\n    \"authz/allow\",           // Rego パッケージ/ルールパス\n    pvendpoint.WithOPATimeout(5 * time.Second),\n)\n```\n\nOPA はベアラートークン、リソース、アクションを `input` オブジェクトで受け取ります：\n\n```json\n{\"input\": {\"resource\": \"posts/123\", \"action\": \"read\", \"token\": \"\u003cbearer\u003e\"}}\n```\n\n`allow` を `true` または `false` に評価する Rego ポリシーを記述してください。\n\n### Cedar agent (permitio/cedar-agent)\n\n```go\nimport \"context\"\n\nverifier, err := pvendpoint.NewCedarAgentEndpoint(\n    \"http://cedar-agent:8180\",\n    pvendpoint.WithCedarAgentPrincipalPrefix(\"User\"),\n    pvendpoint.WithCedarAgentPrincipalResolver(func(ctx context.Context, token string) string {\n        // JWT からサブジェクトを抽出するか、トークンをそのまま返す\n        return token\n    }),\n)\n```\n\nCedar agent は Cedar エンティティ UID を受け取ります：\n\n```json\n{\"principal\": \"User::\\\"subject\\\"\", \"action\": \"Action::\\\"read\\\"\", \"resource\": \"Resource::\\\"posts/123\\\"\"}\n```\n\n### カスタムバックエンド\n\n他の認可システム用に `endpoint.VerifierEndpoint` を実装できます：\n\n```go\ntype VerifierEndpoint interface {\n    Verify(ctx context.Context, resource, action string) error\n}\n```\n\n許可の場合は `nil`、拒否の場合は `status.Error(codes.PermissionDenied, ...)`、認証情報不足の場合は `status.Error(codes.Unauthenticated, ...)` を返してください。\n\n## ライセンス\n\nApache 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo3co%2Fgrpc.authz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fo3co%2Fgrpc.authz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo3co%2Fgrpc.authz/lists"}