Skip to main content

Cerbosの実装レシピ

Cerbosを様々なユースケースで効果的に実装するためのパターンとレシピを紹介します。これらのレシピは、一般的なアクセス制御の課題に対する実践的な解決策を提供します。

基本的なRBACの実装 #

最もシンプルなロールベースのアクセス制御(RBAC)を実装する方法です。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "article:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["reader", "editor", "admin"]
      
    - actions: ["create", "update"]
      effect: EFFECT_ALLOW
      roles: ["editor", "admin"]
      
    - actions: ["delete", "publish"]
      effect: EFFECT_ALLOW
      roles: ["admin"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkAccess(userId, userRoles, articleId) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
    },
    resource: {
      kind: "article:object",
      id: articleId,
    },
    actions: ["view", "update", "delete"],
  });
  
  return {
    canView: decision.isAllowed("view"),
    canUpdate: decision.isAllowed("update"),
    canDelete: decision.isAllowed("delete"),
  };
}

属性ベースのアクセス制御(ABAC) #

リソースと主体の属性に基づいたより柔軟なアクセス制御を実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "document:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["user"]
      condition:
        match:
          expr: request.resource.attr.status == "published" || request.resource.attr.owner == request.principal.id
      
    - actions: ["edit", "delete"]
      effect: EFFECT_ALLOW
      roles: ["user"]
      condition:
        match:
          expr: request.resource.attr.owner == request.principal.id
          
    - actions: ["view", "edit", "delete", "approve"]
      effect: EFFECT_ALLOW
      roles: ["admin"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkDocumentAccess(userId, userRoles, documentId, documentAttributes) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
    },
    resource: {
      kind: "document:object",
      id: documentId,
      attributes: documentAttributes,
    },
    actions: ["view", "edit", "delete"],
  });
  
  return {
    canView: decision.isAllowed("view"),
    canEdit: decision.isAllowed("edit"),
    canDelete: decision.isAllowed("delete"),
  };
}

階層型リソースのアクセス制御 #

フォルダやプロジェクトなどの階層型リソースに対するアクセス制御を実装します。

派生ロール定義 #

---
apiVersion: api.cerbos.dev/v1
derivedRoles:
  name: "project_roles"
  definitions:
    - name: "project_admin"
      parentRoles: ["user"]
      condition:
        match:
          expr: request.resource.attr.project_roles[request.principal.id] == "admin"
          
    - name: "project_member"
      parentRoles: ["user"]
      condition:
        match:
          expr: request.resource.attr.project_roles[request.principal.id] in ["member", "admin"]

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  importDerivedRoles:
    - "project_roles"
  resource: "project:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      derivedRoles: ["project_member"]
      
    - actions: ["edit", "add_member"]
      effect: EFFECT_ALLOW
      derivedRoles: ["project_admin"]
      
    - actions: ["delete"]
      effect: EFFECT_ALLOW
      roles: ["admin"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkProjectAccess(userId, userRoles, projectId, projectAttributes) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
    },
    resource: {
      kind: "project:object",
      id: projectId,
      attributes: projectAttributes,
    },
    actions: ["view", "edit", "add_member", "delete"],
  });
  
  return {
    canView: decision.isAllowed("view"),
    canEdit: decision.isAllowed("edit"),
    canAddMember: decision.isAllowed("add_member"),
    canDelete: decision.isAllowed("delete"),
  };
}

マルチテナントアプリケーション #

複数のテナント(組織)を持つアプリケーションでのアクセス制御を実装します。

派生ロール定義 #

---
apiVersion: api.cerbos.dev/v1
derivedRoles:
  name: "tenant_roles"
  definitions:
    - name: "tenant_admin"
      parentRoles: ["user"]
      condition:
        match:
          expr: request.principal.attr.tenant_roles[request.resource.attr.tenant_id] == "admin"
          
    - name: "tenant_member"
      parentRoles: ["user"]
      condition:
        match:
          expr: request.principal.attr.tenant_roles[request.resource.attr.tenant_id] in ["member", "admin"]

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  importDerivedRoles:
    - "tenant_roles"
  resource: "tenant_resource:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      derivedRoles: ["tenant_member"]
      
    - actions: ["create", "update"]
      effect: EFFECT_ALLOW
      derivedRoles: ["tenant_admin"]
      
    - actions: ["delete"]
      effect: EFFECT_ALLOW
      roles: ["system_admin"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkTenantResourceAccess(userId, userRoles, userAttributes, resourceId, resourceAttributes) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
      attributes: userAttributes,
    },
    resource: {
      kind: "tenant_resource:object",
      id: resourceId,
      attributes: resourceAttributes,
    },
    actions: ["view", "create", "update", "delete"],
  });
  
  return {
    canView: decision.isAllowed("view"),
    canCreate: decision.isAllowed("create"),
    canUpdate: decision.isAllowed("update"),
    canDelete: decision.isAllowed("delete"),
  };
}

時間ベースのアクセス制御 #

特定の時間帯や期間に基づいたアクセス制御を実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "system:object"
  rules:
    - actions: ["access"]
      effect: EFFECT_ALLOW
      roles: ["user"]
      condition:
        match:
          expr: |
            timestamp(request.context.timestamp).getDayOfWeek() >= 1 && 
            timestamp(request.context.timestamp).getDayOfWeek() <= 5 && 
            timestamp(request.context.timestamp).getHours() >= 9 && 
            timestamp(request.context.timestamp).getHours() < 17

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkSystemAccess(userId, userRoles) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
    },
    resource: {
      kind: "system:object",
      id: "main",
    },
    actions: ["access"],
    context: {
      timestamp: new Date().toISOString(),
    },
  });
  
  return {
    canAccess: decision.isAllowed("access"),
  };
}

地理的制限によるアクセス制御 #

ユーザーの地理的位置に基づいたアクセス制御を実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "sensitive_data:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["data_user"]
      condition:
        match:
          expr: request.context.geo_data.country_code in ["JP", "US", "GB"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkDataAccess(userId, userRoles, dataId, geoData) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
    },
    resource: {
      kind: "sensitive_data:object",
      id: dataId,
    },
    actions: ["view"],
    context: {
      geo_data: geoData,
    },
  });
  
  return {
    canView: decision.isAllowed("view"),
  };
}

承認ワークフロー #

複数のステップを持つ承認ワークフローを実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "expense:object"
  rules:
    - actions: ["submit"]
      effect: EFFECT_ALLOW
      roles: ["employee"]
      condition:
        match:
          expr: request.resource.attr.status == "draft" && request.resource.attr.submitter == request.principal.id
      
    - actions: ["approve_first_level"]
      effect: EFFECT_ALLOW
      roles: ["manager"]
      condition:
        match:
          expr: request.resource.attr.status == "submitted" && request.resource.attr.department == request.principal.attr.department
          
    - actions: ["approve_final"]
      effect: EFFECT_ALLOW
      roles: ["finance"]
      condition:
        match:
          expr: request.resource.attr.status == "manager_approved" && request.resource.attr.amount <= 10000
          
    - actions: ["approve_final"]
      effect: EFFECT_ALLOW
      roles: ["finance_director"]
      condition:
        match:
          expr: request.resource.attr.status == "manager_approved"

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function checkExpenseActions(userId, userRoles, userAttributes, expenseId, expenseAttributes) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const decision = await cerbos.checkResource({
    principal: {
      id: userId,
      roles: userRoles,
      attributes: userAttributes,
    },
    resource: {
      kind: "expense:object",
      id: expenseId,
      attributes: expenseAttributes,
    },
    actions: ["submit", "approve_first_level", "approve_final"],
  });
  
  return {
    canSubmit: decision.isAllowed("submit"),
    canApproveFirstLevel: decision.isAllowed("approve_first_level"),
    canApproveFinal: decision.isAllowed("approve_final"),
  };
}

データフィルタリング #

データベースクエリのフィルタリング条件を生成するパターンを実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "customer:object"
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["sales_rep"]
      condition:
        match:
          expr: request.resource.attr.region == request.principal.attr.region
      
    - actions: ["view"]
      effect: EFFECT_ALLOW
      roles: ["sales_manager"]
      
    - actions: ["update"]
      effect: EFFECT_ALLOW
      roles: ["sales_rep"]
      condition:
        match:
          expr: request.resource.attr.region == request.principal.attr.region && request.resource.attr.status != "vip"
          
    - actions: ["update"]
      effect: EFFECT_ALLOW
      roles: ["sales_manager"]

使用例 #

// Node.jsでの使用例
const { GRPC: Cerbos } = require("@cerbos/grpc");

async function getCustomerQueryPlan(userId, userRoles, userAttributes) {
  const cerbos = new Cerbos("localhost:3593", { tls: false });
  
  const plan = await cerbos.planResources({
    principal: {
      id: userId,
      roles: userRoles,
      attributes: userAttributes,
    },
    resource: {
      kind: "customer:object",
    },
    action: "view",
  });
  
  // SQLクエリのWHERE句に変換
  let whereClause = "";
  if (plan.kind === "KIND_CONDITIONAL") {
    // 例: region = 'APAC' に変換
    whereClause = `WHERE ${plan.condition.replace(/request\.resource\.attr\./g, "")}`;
  }
  
  return whereClause;
}

APIゲートウェイとの統合 #

APIゲートウェイでのアクセス制御を実装します。

リソースポリシー #

---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
  version: "default"
  resource: "api:endpoint"
  rules:
    - actions: ["GET"]
      effect: EFFECT_ALLOW
      roles: ["api_user", "api_admin"]
      
    - actions: ["POST", "PUT", "PATCH"]
      effect: EFFECT_ALLOW
      roles: ["api_user", "api_admin"]
      condition:
        match:
          expr: request.resource.attr.rate_limit_tier == "standard" ? request.context.request_count < 1000 : request.context.request_count < 10000
          
    - actions: ["DELETE"]
      effect: EFFECT_ALLOW
      roles: ["api_admin"]

使用例(Kong APIゲートウェイとの統合) #

-- Kong プラグインの例
local http = require "resty.http"
local cjson = require "cjson"

local CerbosAuthHandler = {
  PRIORITY = 1000,
  VERSION = "1.0.0",
}

function CerbosAuthHandler:access(conf)
  local client = http.new()
  local headers = ngx.req.get_headers()
  local jwt_token = headers["Authorization"]
  
  -- JWTからユーザー情報を抽出
  local user_id, user_roles = extract_from_jwt(jwt_token)
  
  -- Cerbosにリクエスト
  local res, err = client:request_uri(conf.cerbos_url, {
    method = "POST",
    path = "/api/check/resource",
    body = cjson.encode({
      principal = {
        id = user_id,
        roles = user_roles,
      },
      resource = {
        kind = "api:endpoint",
        id = ngx.var.request_uri,
        attributes = {
          rate_limit_tier = get_user_tier(user_id),
        },
      },
      actions = { ngx.req.get_method() },
      context = {
        request_count = get_request_count(user_id),
      },
    }),
    headers = {
      ["Content-Type"] = "application/json",
    },
  })
  
  if not res or res.status ~= 200 then
    return kong.response.exit(500, { message = "Authorization service error" })
  end
  
  local body = cjson.decode(res.body)
  if body.results[ngx.req.get_method()] ~= "EFFECT_ALLOW" then
    return kong.response.exit(403, { message = "Access denied" })
  end
end

return CerbosAuthHandler

マイクロサービスでの実装 #

マイクロサービスアーキテクチャでのCerbos実装パターンを示します。

サイドカーパターン #

各マイクロサービスにCerbosをサイドカーとしてデプロイします。

# Kubernetes Deployment例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: my-company/user-service:latest
        ports:
        - containerPort: 8080
      - name: cerbos
        image: ghcr.io/cerbos/cerbos:0.41.0
        args: ["server"]
        ports:
        - containerPort: 3592
          name: http
        - containerPort: 3593
          name: grpc
        volumeMounts:
        - name: policies
          mountPath: /policies
      volumes:
      - name: policies
        configMap:
          name: cerbos-policies

使用例(Javaマイクロサービス) #

// Javaでの使用例
import dev.cerbos.sdk.CerbosBlockingClient;
import dev.cerbos.sdk.CerbosClientBuilder;

@Service
public class AuthorizationService {
    private final CerbosBlockingClient cerbosClient;
    
    public AuthorizationService() {
        this.cerbosClient = new CerbosClientBuilder("localhost:3593")
            .withPlaintext()
            .buildBlockingClient();
    }
    
    public boolean isAllowed(String userId, List<String> roles, Map<String, Object> userAttributes,
                            String resourceKind, String resourceId, Map<String, Object> resourceAttributes,
                            String action) {
        Principal principal = Principal.newInstance(userId, roles)
            .withAttributes(convertAttributes(userAttributes));
            
        Resource resource = Resource.newInstance(resourceKind, resourceId)
            .withAttributes(convertAttributes(resourceAttributes));
            
        CheckResult result = cerbosClient.checkResource(principal, resource, new String[]{action});
        return result.isAllowed(action);
    }
    
    // 属性変換ヘルパーメソッド
    private Map<String, AttributeValue> convertAttributes(Map<String, Object> attributes) {
        // 実装省略
    }
}

まとめ #

これらのレシピは、Cerbosを使用して様々なアクセス制御パターンを実装する方法を示しています。実際のアプリケーションでは、これらのパターンを組み合わせたり、拡張したりして、特定の要件に合わせたアクセス制御システムを構築することができます。

Cerbosの柔軟性により、シンプルなRBACから複雑なABACまで、様々なアクセス制御モデルを実装できます。また、マイクロサービスアーキテクチャやAPIゲートウェイなど、現代的なアプリケーションアーキテクチャとの統合も容易です。

[出典: https://docs.cerbos.dev/cerbos/latest/recipes/]