zero-trust について
zero-trustは、ゼロトラストアーキテクチャの設計と実装を支援するセキュリティスキルです。内部アプリやクラウドワークロード向けに、ID確認、mTLS、ネットワークポリシー、継続的な監視を扱います。
主な機能
- 01認証と認可
- 02ID認識型プロキシの設定
- 03サービス間のmTLS
- 04Kubernetesネットワークのマイクロセグメンテーション
- 05OPAアクセス制御ポリシー
- 06実装とトラブルシューティングの手引き
活用シーン
- 01境界型VPNアクセスの置き換え
- 02マルチクラウドとハイブリッド環境の保護
- 03ネットワークポリシーによるKubernetesワークロードの分離
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ bagelhole/ devops-security-agent-skills --skill zero-trustインストール後、Claude Code で/zero-trustを入力して読み込まれたか確認します。
詳細
ゼロトラストアーキテクチャ
「決して信頼せず、常に検証する」セキュリティモデルを実装します。
このスキルを使用する場面
次のような場合に、このスキルを使用します。
- 従来の境界型 VPN アクセスモデルを置き換える
- BeyondCorp スタイルの社内アプリケーションへのアクセスを実装する
- マルチクラウドまたはハイブリッドクラウド環境を保護する
- すべてのサービス間通信で ID ベースのアクセス制御を適用する
- 継続的な検証と最小権限に関するコンプライアンス要件を満たす
- Kubernetes またはクラウドワークロードにマイクロセグメンテーションを導入する
前提条件
- OIDC/SAML をサポートする ID プロバイダー(IdP)(Okta、Azure AD、Google Workspace)
- サービスメッシュまたはプロキシインフラストラクチャ(Istio、Envoy、Cloudflare Access)
- デバイスポスチャチェック用のデバイス管理/MDM ソリューション
- ワークロードレベルの例で使用する Kubernetes クラスター
- mTLS、RBAC、ネットワークポリシーの理解
基本原則
zero_trust_principles:
verify_explicitly:
description: "Authenticate and authorize every access request"
controls:
- Strong multi-factor authentication
- Identity-aware proxy for all applications
- Service-to-service mTLS
- API token validation on every request
least_privilege:
description: "Grant minimum access needed for the task"
controls:
- Just-in-time (JIT) access provisioning
- Time-bounded access grants
- Role-based access with fine-grained permissions
- Regular access reviews and certification
assume_breach:
description: "Design systems expecting compromise has occurred"
controls:
- Micro-segmentation between all services
- End-to-end encryption (data in transit and at rest)
- Continuous monitoring and anomaly detection
- Blast radius containment
BeyondCorp の実装
Cloudflare Access の設定
# Create an Access application for an internal service
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/access/apps" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Internal Dashboard",
"domain": "dashboard.internal.example.com",
"type": "self_hosted",
"session_duration": "12h",
"auto_redirect_to_identity": true,
"allowed_idps": ["google-workspace-idp-id"]
}'
# Create an Access policy
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/access/apps/${APP_ID}/policies" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Engineering team access",
"decision": "allow",
"include": [
{ "group": { "id": "engineering-group-id" } }
],
"require": [
{ "login_method": { "id": "google-workspace-idp-id" } }
],
"exclude": [
{ "geo": { "country_code": "KP" } }
]
}'
# Create a device posture rule
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/devices/posture" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Require disk encryption",
"type": "disk_encryption",
"match": { "platform": "linux" },
"schedule": "1h",
"input": { "requireAll": true }
}'
Cloudflare Access の Terraform
resource "cloudflare_access_application" "dashboard" {
account_id = var.cloudflare_account_id
name = "Internal Dashboard"
domain = "dashboard.internal.example.com"
type = "self_hosted"
session_duration = "12h"
auto_redirect_to_identity = true
}
resource "cloudflare_access_policy" "engineering" {
account_id = var.cloudflare_account_id
application_id = cloudflare_access_application.dashboard.id
name = "Engineering team"
precedence = 1
decision = "allow"
include {
group = [cloudflare_access_group.engineering.id]
}
require {
login_method = [var.google_idp_id]
}
}
resource "cloudflare_access_group" "engineering" {
account_id = var.cloudflare_account_id
name = "Engineering"
include {
email_domain = ["example.com"]
}
require {
group = ["[email protected]"]
}
}
OAuth2 Proxy を使用した ID 認識型プロキシ
# oauth2-proxy deployment for protecting internal services
apiVersion: apps/v1
kind: Deployment
metadata:
name: oauth2-proxy
namespace: auth
spec:
replicas: 2
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
args:
- --provider=oidc
- --oidc-issuer-url=https://accounts.google.com
- --client-id=$(CLIENT_ID)
- --client-secret=$(CLIENT_SECRET)
- --email-domain=example.com
- --upstream=http://internal-service.default.svc:8080
- --http-address=0.0.0.0:4180
- --cookie-secret=$(COOKIE_SECRET)
- --cookie-secure=true
- --cookie-httponly=true
- --cookie-samesite=lax
- --set-xauthrequest=true
- --pass-access-token=true
- --skip-provider-button=true
- --session-store-type=redis
- --redis-connection-url=redis://redis.auth.svc:6379
env:
- name: CLIENT_ID
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: client-id
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: client-secret
- name: COOKIE_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy
key: cookie-secret
ports:
- containerPort: 4180
---
# Ingress routing through oauth2-proxy
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: internal-service
annotations:
nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://auth.example.com/oauth2/start?rd=$scheme://$host$request_uri"
nginx.ingress.kubernetes.io/auth-response-headers: "X-Auth-Request-User,X-Auth-Request-Email"
spec:
rules:
- host: dashboard.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: internal-service
port:
number: 8080
サービスメッシュの mTLS(Istio)
# Enforce strict mTLS across the mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
---
# Authorization policy: frontend can call backend
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-access
namespace: default
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/default/sa/frontend"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]
---
# Default deny all in namespace
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: production
spec: {}
Kubernetes ネットワークポリシーによるマイクロセグメンテーション
# Default deny all traffic in namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow DNS resolution for all pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Frontend: allow ingress from ingress controller, egress to backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 8080
---
# Database: allow from backend only, no egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
アクセス判断のための OPA ポリシー
# policy.rego - Zero trust access decision
package zerotrust.access
import rego.v1
default allow := false
allow if {
identity_verified
device_compliant
authorized_for_resource
risk_acceptable
}
identity_verified if {
input.identity.authenticated == true
input.identity.mfa_verified == true
time.now_ns() < input.identity.session_expires_ns
}
device_compliant if {
input.device.encryption_enabled == true
input.device.os_updated == true
input.device.firewall_enabled == true
input.device.certificate_valid == true
}
authorized_for_resource if {
some role in input.identity.roles
some permission in data.role_permissions[role]
permission == input.resource.required_permission
}
risk_acceptable if {
input.risk.score < 70
not input.risk.active_threat
}
step_up_required if {
input.risk.score >= 50
input.risk.score < 70
not input.identity.recent_mfa
}
実装手順
- アセットとデータフローを棚卸しする - すべてのアプリケーション、サービス、データストアをマッピングします
- ID プロバイダーをデプロイする - SSO と MFA を使用して認証を一元化します
- ID 認識型プロキシを実装する - すべてのアクセスを認証レイヤー経由でルーティングします
- サービスメッシュで mTLS を有効にする - すべてのサービス通信を暗号化し、認証します
- ネットワークポリシーを適用する - デフォルトで拒否し、明示的な許可ルールを設定します
- デバイスポスチャチェックを追加する - アクセスを許可する前に、デバイスが要件に準拠していることを確認します
- 継続的なモニタリングを導入する - すべてのアクセス判断を記録し、分析します
- 反復して改善する - モニタリングデータに基づいてポリシーを見直します
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| ユーザーが社内アプリにアクセスできない | ID プロバイダーの設定ミス | OIDC/SAML の設定を確認し、リダイレクト URI を確認します |
| mTLS 接続が失敗する | 証明書の有効期限切れ、または CA の誤り | istioctl proxy-config secret で証明書の有効期限を確認し、CA チェーンを検証します |
| ネットワークポリシーが正当なトラフィックをブロックする | エグレスルールまたはイングレスルールが不足している | kubectl describe networkpolicy を使用し、Pod のラベルがセレクターと一致することを確認します |
| デバイスポスチャチェックに失敗する | MDM エージェントから報告がない | デバイスエージェントが実行中であることを確認し、コンプライアンスダッシュボードを確認します |
| OAuth2 プロキシが 403 を返す | ユーザーのメールドメインが許可リストにない | --email-domain フラグにドメインを追加するか、グループメンバーシップを更新します |
関連スキル
- サービスメッシュ - mTLS の実装
- kubernetes-hardening - K8s のセキュリティ
- vpn-setup - 従来の VPN(ゼロトラストとの比較)
コメント
Markdown 対応