refactor: reorganize GitOps control plane
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
name: Promote Dev Image by Pull Request
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
service:
|
||||||
|
description: Workload to promote
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- auth-server
|
||||||
|
- api-server
|
||||||
|
image_digest:
|
||||||
|
description: Immutable OCI digest including the sha256 prefix
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: promote-dev-${{ inputs.service }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
promote:
|
||||||
|
runs-on:
|
||||||
|
- self-hosted
|
||||||
|
- linux
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITOPS_BOT_TOKEN }}
|
||||||
|
|
||||||
|
- name: Update immutable digest
|
||||||
|
env:
|
||||||
|
SERVICE: ${{ inputs.service }}
|
||||||
|
IMAGE_DIGEST: ${{ inputs.image_digest }}
|
||||||
|
run: |
|
||||||
|
case "$SERVICE" in
|
||||||
|
auth-server)
|
||||||
|
image_name="ghcr.io/donghyeonka/project-auth-server"
|
||||||
|
;;
|
||||||
|
api-server)
|
||||||
|
image_name="ghcr.io/donghyeonka/project-api-server"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported service: $SERVICE" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [[ ! "$IMAGE_DIGEST" =~ ^sha256:[a-f0-9]{64}$ ]]; then
|
||||||
|
echo "image_digest must be an immutable sha256 digest" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
manifest="clusters/dev-k3s/manifests/${SERVICE}/kustomization.yaml"
|
||||||
|
temporary="$(mktemp "${manifest}.XXXXXX")"
|
||||||
|
awk -v image_name="$image_name" -v image_digest="$IMAGE_DIGEST" '
|
||||||
|
$1 == "-" && $2 == "name:" { target = ($3 == image_name) }
|
||||||
|
target && $1 == "newTag:" {
|
||||||
|
match($0, /^[[:space:]]*/)
|
||||||
|
print substr($0, RSTART, RLENGTH) "digest: " image_digest
|
||||||
|
target = 0
|
||||||
|
updated = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
target && $1 == "digest:" {
|
||||||
|
match($0, /^[[:space:]]*/)
|
||||||
|
print substr($0, RSTART, RLENGTH) "digest: " image_digest
|
||||||
|
target = 0
|
||||||
|
updated = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END { if (!updated) exit 42 }
|
||||||
|
' "$manifest" >"$temporary"
|
||||||
|
mv "$temporary" "$manifest"
|
||||||
|
kubectl kustomize "$(dirname "$manifest")" >/dev/null
|
||||||
|
|
||||||
|
- name: Create promotion branch
|
||||||
|
env:
|
||||||
|
SERVICE: ${{ inputs.service }}
|
||||||
|
RUN_NUMBER: ${{ gitea.run_number }}
|
||||||
|
run: |
|
||||||
|
branch="gitops/promote-${SERVICE}-${RUN_NUMBER}"
|
||||||
|
git config user.name "gitops-bot"
|
||||||
|
git config user.email "gitops-bot@hyeonworks.local"
|
||||||
|
git switch -c "$branch"
|
||||||
|
git add "clusters/dev-k3s/manifests/${SERVICE}/kustomization.yaml"
|
||||||
|
git commit -m "chore(gitops): promote ${SERVICE} dev digest"
|
||||||
|
git push --set-upstream origin "$branch"
|
||||||
|
echo "PROMOTION_BRANCH=$branch" >>"$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Open Gitea pull request
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITOPS_BOT_TOKEN }}
|
||||||
|
GITEA_API_URL: ${{ gitea.api_url }}
|
||||||
|
REPOSITORY: ${{ gitea.repository }}
|
||||||
|
DEFAULT_BRANCH: ${{ gitea.event.repository.default_branch }}
|
||||||
|
SERVICE: ${{ inputs.service }}
|
||||||
|
IMAGE_DIGEST: ${{ inputs.image_digest }}
|
||||||
|
run: |
|
||||||
|
payload="$(
|
||||||
|
jq -n \
|
||||||
|
--arg base "$DEFAULT_BRANCH" \
|
||||||
|
--arg head "$PROMOTION_BRANCH" \
|
||||||
|
--arg title "chore(gitops): promote ${SERVICE} dev digest" \
|
||||||
|
--arg body "Promotes ${SERVICE} to immutable digest ${IMAGE_DIGEST}." \
|
||||||
|
'{base: $base, head: $head, title: $title, body: $body}'
|
||||||
|
)"
|
||||||
|
curl -fsS \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
"${GITEA_API_URL}/repos/${REPOSITORY}/pulls"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: Validate GitOps Repository
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: validate-gitops-${{ gitea.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on:
|
||||||
|
- self-hosted
|
||||||
|
- linux
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Validate
|
||||||
|
run: ./hack/validate.sh
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
# Terraform working directories and local state
|
||||||
|
**/.terraform/*
|
||||||
|
**/.terraform-state/*
|
||||||
|
*.tfstate
|
||||||
|
*.tfstate.*
|
||||||
|
*.tfplan
|
||||||
|
*.tfvars
|
||||||
|
!*.tfvars.example
|
||||||
|
crash.log
|
||||||
|
crash.*.log
|
||||||
|
|
||||||
|
# Bootstrap material and local operator files
|
||||||
|
.local/*
|
||||||
|
!.local/.gitkeep
|
||||||
|
*init*.json
|
||||||
|
|
||||||
|
# Tool and editor output
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.tmp
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Repository operating rules
|
||||||
|
|
||||||
|
- Treat the Gitea `origin` as the canonical deployment repository.
|
||||||
|
- Production is disabled; do not create or enable production Applications
|
||||||
|
without a complete production design and explicit approval.
|
||||||
|
- Never commit Terraform state, provider directories, plan files, tfvars,
|
||||||
|
Vault init JSON, unseal/recovery material or plaintext credentials.
|
||||||
|
- A Vault API object may be owned by only one Terraform state.
|
||||||
|
- Keep secret payloads outside Terraform resources and data sources.
|
||||||
|
- Routine GitOps automation changes Git only; direct cluster mutation is
|
||||||
|
reserved for documented bootstrap and recovery runbooks.
|
||||||
|
- Run `make validate` before handing off repository changes.
|
||||||
|
- Do not apply to a live cluster unless the user explicitly requests live
|
||||||
|
deployment and the kube context has been verified.
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-07-25
|
||||||
|
|
||||||
|
- Established the internal Gitea repository as the single GitOps source.
|
||||||
|
- Replaced staged Argo roots with one bootstrap seed and one cluster-owned
|
||||||
|
root Application.
|
||||||
|
- Reorganized the repository around `clusters/dev-k3s`, environment-neutral
|
||||||
|
`platform`/`workloads` bases and separate `iac/terraform`.
|
||||||
|
- Corrected the Sealed Secrets Helm repository and added controller resources
|
||||||
|
and key renewal configuration.
|
||||||
|
- Removed incomplete production skeletons; production remains unsupported.
|
||||||
|
- Changed application promotion to a Gitea pull request carrying an immutable
|
||||||
|
OCI digest and removed direct writes to `main`.
|
||||||
|
- Pinned Vault, PostgreSQL, Keycloak, Vault Injector and Sealed Secrets images
|
||||||
|
to verified multi-architecture digests.
|
||||||
|
- Removed routine `kubectl apply`, port-forward orchestration and reusable
|
||||||
|
Vault operator-token scripts.
|
||||||
|
- Added a guarded, dev-only Vault init/unseal/root-revoke entrypoint.
|
||||||
|
- Fixed auth migration and Keycloak sync Job lifecycle and ordering.
|
||||||
|
- Removed selective sync from Applications containing Sync hooks.
|
||||||
|
- Enabled ConfigMap hash rollouts and projected Vault authentication tokens.
|
||||||
|
- Removed the same-cluster Transit Vault and its credential rotation cycle.
|
||||||
|
- Consolidated Terraform into `vault-core` and `vault-database` remote states
|
||||||
|
with write-only/ephemeral credential inputs.
|
||||||
|
- Removed tracked Terraform providers and backend metadata.
|
||||||
|
- Moved machine-consumed Vault policies out of runbooks and narrowed routine
|
||||||
|
automation permissions.
|
||||||
|
- Recorded Gateway API first and deferred Istio ambient adoption criteria.
|
||||||
|
- Reduced shell entrypoints from 18 files/1,205 lines to 3 files/300 lines.
|
||||||
|
- Split current documentation from archived historical material.
|
||||||
+46
-3016
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
|||||||
|
.PHONY: bootstrap validate vault-init terraform-init terraform-plan terraform-apply check-terraform-inputs
|
||||||
|
|
||||||
|
KUBE_CONTEXT ?=
|
||||||
|
TF_ROOT ?=
|
||||||
|
BACKEND_CONFIG ?=
|
||||||
|
TF_DIR = iac/terraform/live/dev-k3s/$(TF_ROOT)
|
||||||
|
|
||||||
|
bootstrap:
|
||||||
|
@test -n "$(KUBE_CONTEXT)" || (echo "KUBE_CONTEXT is required" >&2; exit 1)
|
||||||
|
./hack/bootstrap-argocd.sh --context "$(KUBE_CONTEXT)"
|
||||||
|
|
||||||
|
validate:
|
||||||
|
./hack/validate.sh
|
||||||
|
|
||||||
|
vault-init:
|
||||||
|
./hack/vault-init.sh init
|
||||||
|
|
||||||
|
check-terraform-inputs:
|
||||||
|
@case "$(TF_ROOT)" in \
|
||||||
|
vault-core|vault-database) ;; \
|
||||||
|
*) echo "TF_ROOT must be vault-core or vault-database" >&2; exit 1 ;; \
|
||||||
|
esac
|
||||||
|
@test -f "$(BACKEND_CONFIG)" || (echo "BACKEND_CONFIG must point to a readable backend file" >&2; exit 1)
|
||||||
|
|
||||||
|
terraform-init: check-terraform-inputs
|
||||||
|
terraform -chdir="$(TF_DIR)" init -input=false -reconfigure -backend-config="$(abspath $(BACKEND_CONFIG))"
|
||||||
|
|
||||||
|
terraform-plan: terraform-init
|
||||||
|
terraform -chdir="$(TF_DIR)" plan -input=false -lock-timeout=5m
|
||||||
|
|
||||||
|
terraform-apply: terraform-init
|
||||||
|
@test "$(APPROVE_APPLY)" = "dev-k3s/$(TF_ROOT)" || \
|
||||||
|
(echo "Set APPROVE_APPLY=dev-k3s/$(TF_ROOT) to continue" >&2; exit 1)
|
||||||
|
terraform -chdir="$(TF_DIR)" apply -input=false -lock-timeout=5m
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: api-server-config
|
|
||||||
data:
|
|
||||||
APP_SERVER_PORT: "8082"
|
|
||||||
APP_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: http://auth-public.auth-dev.svc.cluster.local
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
|
|
||||||
namespace: api-prod
|
|
||||||
|
|
||||||
resources:
|
|
||||||
- ../../base
|
|
||||||
- namespace.yaml
|
|
||||||
|
|
||||||
images:
|
|
||||||
- name: ghcr.io/donghyeonka/project-api-server
|
|
||||||
newName: ghcr.io/donghyeonka/project-api-server
|
|
||||||
newTag: fd097c9
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: api-prod
|
|
||||||
labels:
|
|
||||||
pod-security.kubernetes.io/enforce: baseline
|
|
||||||
pod-security.kubernetes.io/enforce-version: latest
|
|
||||||
pod-security.kubernetes.io/warn: restricted
|
|
||||||
pod-security.kubernetes.io/warn-version: latest
|
|
||||||
pod-security.kubernetes.io/audit: restricted
|
|
||||||
pod-security.kubernetes.io/audit-version: latest
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: auth-server-config
|
|
||||||
data:
|
|
||||||
SPRING_PROFILES_ACTIVE: dev
|
|
||||||
SERVER_FORWARD_HEADERS_STRATEGY: framework
|
|
||||||
APP_DOCS_TITLE: Project Auth Server API
|
|
||||||
APP_DOCS_DESCRIPTION: dev auth-server OpenAPI
|
|
||||||
APP_DOCS_VERSION: v1
|
|
||||||
APP_DATASOURCE_URL: jdbc:postgresql://postgres.platform.svc.cluster.local:5432/project_auth
|
|
||||||
APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP: "false"
|
|
||||||
APP_SECURITY_OAUTH2_KEYCLOAK_ISSUER_URI: http://keycloak-public.platform.svc.cluster.local/realms/project-auth
|
|
||||||
APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_ID: project-auth-server
|
|
||||||
APP_SECURITY_OAUTH2_GOOGLE_REGISTRATION_ID: keycloak-google
|
|
||||||
APP_SECURITY_OAUTH2_GOOGLE_IDP_HINT: google
|
|
||||||
APP_SECURITY_OAUTH2_GITHUB_REGISTRATION_ID: keycloak-github
|
|
||||||
APP_SECURITY_OAUTH2_GITHUB_IDP_HINT: github
|
|
||||||
APP_SECURITY_JWT_ISSUER: http://auth-public.auth-dev.svc.cluster.local
|
|
||||||
APP_SECURITY_JWT_ACTIVE_KEY_ID: dev-vault-rsa-1
|
|
||||||
APP_SECURITY_JWT_GENERATE_KEY_PAIR_ON_STARTUP: "false"
|
|
||||||
APP_SECURITY_JWT_ACCESS_TOKEN_EXPIRATION: PT30M
|
|
||||||
APP_SECURITY_JWT_VAULT_ENABLED: "true"
|
|
||||||
APP_SECURITY_JWT_VAULT_ADDRESS: http://vault.vault.svc.cluster.local:8200
|
|
||||||
APP_SECURITY_JWT_VAULT_MOUNT_PATH: transit
|
|
||||||
APP_SECURITY_JWT_VAULT_TRANSIT_KEY_NAME: project-auth-jwt
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
|
|
||||||
namespace: auth-prod
|
|
||||||
|
|
||||||
resources:
|
|
||||||
- ../../base
|
|
||||||
- namespace.yaml
|
|
||||||
|
|
||||||
images:
|
|
||||||
- name: ghcr.io/donghyeonka/project-auth-server
|
|
||||||
newName: ghcr.io/donghyeonka/project-auth-server
|
|
||||||
newTag: 5648fd2
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: auth-prod
|
|
||||||
labels:
|
|
||||||
pod-security.kubernetes.io/enforce: baseline
|
|
||||||
pod-security.kubernetes.io/enforce-version: latest
|
|
||||||
pod-security.kubernetes.io/warn: restricted
|
|
||||||
pod-security.kubernetes.io/warn-version: latest
|
|
||||||
pod-security.kubernetes.io/audit: restricted
|
|
||||||
pod-security.kubernetes.io/audit-version: latest
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
## Argo CD Structure
|
|
||||||
|
|
||||||
`argocd/` 디렉터리는 환경(`dev`, `prod`)과 성격(`apps`, `infra`) 기준으로 나눠 관리합니다.
|
|
||||||
|
|
||||||
- `applications/<env>/apps`: 서비스 애플리케이션 선언
|
|
||||||
- `applications/<env>/infra`: 공용 인프라/컨트롤러 선언
|
|
||||||
- `projects/<env>/apps-project.yaml`: 서비스 애플리케이션용 AppProject
|
|
||||||
- `projects/<env>/infra-project.yaml`: 공용 인프라용 AppProject
|
|
||||||
|
|
||||||
현재 `dev`에는 실제 선언을 두고, `prod`는 이후 운영 확장을 위한 구조와 프로젝트 골격을 먼저 유지합니다.
|
|
||||||
|
|
||||||
현재 dev `infra`에는 대표적으로 아래 Application이 포함됩니다.
|
|
||||||
|
|
||||||
- `vault-transit`: workload Vault transit auto-unseal provider
|
|
||||||
- `vault`: workload Vault
|
|
||||||
- `platform`: postgres, keycloak
|
|
||||||
- `vault-agent-injector`: workload secret injection
|
|
||||||
- `sealed-secrets`: image pull secret 같은 예외 secret 처리
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: Application
|
|
||||||
metadata:
|
|
||||||
name: vault-transit-dev
|
|
||||||
namespace: argocd
|
|
||||||
annotations:
|
|
||||||
argocd.argoproj.io/sync-wave: "10"
|
|
||||||
finalizers:
|
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
|
||||||
project: infra-dev
|
|
||||||
source:
|
|
||||||
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
|
|
||||||
targetRevision: main
|
|
||||||
path: infra/vault-transit/overlays/dev
|
|
||||||
destination:
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
namespace: vault-transit
|
|
||||||
syncPolicy:
|
|
||||||
automated:
|
|
||||||
enabled: true
|
|
||||||
prune: true
|
|
||||||
selfHeal: true
|
|
||||||
syncOptions:
|
|
||||||
- CreateNamespace=true
|
|
||||||
- PruneLast=true
|
|
||||||
- ApplyOutOfSyncOnly=true
|
|
||||||
retry:
|
|
||||||
limit: 5
|
|
||||||
backoff:
|
|
||||||
duration: 5s
|
|
||||||
factor: 2
|
|
||||||
maxDuration: 3m
|
|
||||||
revisionHistoryLimit: 5
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: AppProject
|
|
||||||
metadata:
|
|
||||||
name: infra-dev
|
|
||||||
namespace: argocd
|
|
||||||
finalizers:
|
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
|
||||||
description: Dev shared infrastructure managed by Argo CD
|
|
||||||
sourceRepos:
|
|
||||||
- https://github.com/DongHyeonka/Project-Auth-GitOps
|
|
||||||
- https://bitnami-labs.github.io/sealed-secrets
|
|
||||||
- https://helm.releases.hashicorp.com
|
|
||||||
destinations:
|
|
||||||
- namespace: platform
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
- namespace: vault
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
- namespace: vault-transit
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
- namespace: kube-system
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
clusterResourceWhitelist:
|
|
||||||
- group: ""
|
|
||||||
kind: Namespace
|
|
||||||
- group: "apiextensions.k8s.io"
|
|
||||||
kind: CustomResourceDefinition
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
|
||||||
kind: ClusterRole
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
- group: "admissionregistration.k8s.io"
|
|
||||||
kind: MutatingWebhookConfiguration
|
|
||||||
namespaceResourceWhitelist:
|
|
||||||
- group: ""
|
|
||||||
kind: ConfigMap
|
|
||||||
- group: ""
|
|
||||||
kind: Secret
|
|
||||||
- group: ""
|
|
||||||
kind: Service
|
|
||||||
- group: ""
|
|
||||||
kind: ServiceAccount
|
|
||||||
- group: ""
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
- group: "bitnami.com"
|
|
||||||
kind: SealedSecret
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
|
||||||
kind: Role
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
|
||||||
kind: RoleBinding
|
|
||||||
- group: "apps"
|
|
||||||
kind: Deployment
|
|
||||||
- group: "apps"
|
|
||||||
kind: StatefulSet
|
|
||||||
- group: "apps"
|
|
||||||
kind: ReplicaSet
|
|
||||||
- group: "autoscaling"
|
|
||||||
kind: HorizontalPodAutoscaler
|
|
||||||
- group: "batch"
|
|
||||||
kind: Job
|
|
||||||
- group: "networking.k8s.io"
|
|
||||||
kind: Ingress
|
|
||||||
- group: "networking.k8s.io"
|
|
||||||
kind: NetworkPolicy
|
|
||||||
- group: "policy"
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
orphanedResources:
|
|
||||||
warn: true
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
|
||||||
kind: AppProject
|
|
||||||
metadata:
|
|
||||||
name: apps-prod
|
|
||||||
namespace: argocd
|
|
||||||
finalizers:
|
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
|
||||||
description: Prod application workloads managed by Argo CD
|
|
||||||
sourceRepos:
|
|
||||||
- https://github.com/DongHyeonka/Project-Auth-GitOps
|
|
||||||
destinations:
|
|
||||||
- namespace: auth-prod
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
- namespace: api-prod
|
|
||||||
server: https://kubernetes.default.svc
|
|
||||||
clusterResourceWhitelist:
|
|
||||||
- group: ""
|
|
||||||
kind: Namespace
|
|
||||||
namespaceResourceWhitelist:
|
|
||||||
- group: ""
|
|
||||||
kind: ConfigMap
|
|
||||||
- group: ""
|
|
||||||
kind: Secret
|
|
||||||
- group: ""
|
|
||||||
kind: Service
|
|
||||||
- group: ""
|
|
||||||
kind: ServiceAccount
|
|
||||||
- group: ""
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
- group: "bitnami.com"
|
|
||||||
kind: SealedSecret
|
|
||||||
- group: "apps"
|
|
||||||
kind: Deployment
|
|
||||||
- group: "apps"
|
|
||||||
kind: StatefulSet
|
|
||||||
- group: "apps"
|
|
||||||
kind: ReplicaSet
|
|
||||||
- group: "autoscaling"
|
|
||||||
kind: HorizontalPodAutoscaler
|
|
||||||
- group: "batch"
|
|
||||||
kind: Job
|
|
||||||
- group: "networking.k8s.io"
|
|
||||||
kind: Ingress
|
|
||||||
- group: "networking.k8s.io"
|
|
||||||
kind: NetworkPolicy
|
|
||||||
- group: "policy"
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
orphanedResources:
|
|
||||||
warn: true
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: project-gitops-dev-k3s
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
|
targetRevision: main
|
||||||
|
path: clusters/dev-k3s
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: argocd
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
enabled: true
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- PruneLast=true
|
||||||
|
- FailOnSharedResource=true
|
||||||
|
revisionHistoryLimit: 10
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ARGOCD_VERSION=v3.4.2
|
||||||
|
ARGOCD_INSTALL_SHA256=69114b8c9eb48a1d08598e6f654a0869b10ae902456ea4b70796cb563760f5ec
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- sealed-secrets.yaml
|
||||||
|
- vault.yaml
|
||||||
|
- vault-agent-injector.yaml
|
||||||
+18
-4
@@ -3,17 +3,31 @@ kind: Application
|
|||||||
metadata:
|
metadata:
|
||||||
name: sealed-secrets-dev
|
name: sealed-secrets-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
project: infra-dev
|
project: cluster-addons-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://bitnami-labs.github.io/sealed-secrets
|
repoURL: https://bitnami.github.io/sealed-secrets
|
||||||
chart: sealed-secrets
|
chart: sealed-secrets
|
||||||
targetRevision: 2.17.9
|
targetRevision: 2.17.9
|
||||||
helm:
|
helm:
|
||||||
values: |
|
values: |
|
||||||
fullnameOverride: sealed-secrets-controller
|
fullnameOverride: sealed-secrets-controller
|
||||||
|
keyrenewperiod: 720h
|
||||||
|
image:
|
||||||
|
repository: bitnami/sealed-secrets-controller
|
||||||
|
tag: "0.33.1@sha256:e7fad65c2d2f47e48d9ca17408ed56961bfa6a6dd74ccd4a1a214664156534bc"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 256Mi
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
@@ -23,11 +37,11 @@ spec:
|
|||||||
prune: true
|
prune: true
|
||||||
selfHeal: true
|
selfHeal: true
|
||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
+9
-5
@@ -4,11 +4,12 @@ metadata:
|
|||||||
name: vault-agent-injector-dev
|
name: vault-agent-injector-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
annotations:
|
annotations:
|
||||||
argocd.argoproj.io/sync-wave: "10"
|
argocd.argoproj.io/sync-wave: "2"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
project: infra-dev
|
project: cluster-addons-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://helm.releases.hashicorp.com
|
repoURL: https://helm.releases.hashicorp.com
|
||||||
chart: vault
|
chart: vault
|
||||||
@@ -23,6 +24,9 @@ spec:
|
|||||||
injector:
|
injector:
|
||||||
enabled: true
|
enabled: true
|
||||||
authPath: auth/kubernetes
|
authPath: auth/kubernetes
|
||||||
|
image:
|
||||||
|
repository: hashicorp/vault-k8s
|
||||||
|
tag: "1.7.2@sha256:ae3d307658b72a1cf35dab9bdf92c995d45cdc7183af0516857714b5bd0ba84d"
|
||||||
webhook:
|
webhook:
|
||||||
failurePolicy: Fail
|
failurePolicy: Fail
|
||||||
namespaceSelector:
|
namespaceSelector:
|
||||||
@@ -37,7 +41,7 @@ spec:
|
|||||||
memory: 256Mi
|
memory: 256Mi
|
||||||
agentImage:
|
agentImage:
|
||||||
repository: hashicorp/vault
|
repository: hashicorp/vault
|
||||||
tag: "1.18"
|
tag: "1.18.5@sha256:750bb37c1638fa194ab37053a81618c61bb0491ddec6fccac87c07a8e6cd8166"
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: vault
|
namespace: vault
|
||||||
@@ -47,11 +51,11 @@ spec:
|
|||||||
prune: true
|
prune: true
|
||||||
selfHeal: true
|
selfHeal: true
|
||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
+7
-7
@@ -4,15 +4,14 @@ metadata:
|
|||||||
name: vault-dev
|
name: vault-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
annotations:
|
annotations:
|
||||||
argocd.argoproj.io/sync-wave: "10"
|
argocd.argoproj.io/sync-wave: "1"
|
||||||
finalizers:
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
spec:
|
||||||
project: infra-dev
|
project: platform-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
|
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
path: infra/vault/overlays/dev
|
path: clusters/dev-k3s/manifests/vault
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: vault
|
namespace: vault
|
||||||
@@ -24,9 +23,10 @@ spec:
|
|||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
- CreateNamespace=true
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
+3
-4
@@ -1,8 +1,7 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
|
|
||||||
namespace: vault-prod
|
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- foundation
|
||||||
- namespace.yaml
|
- platform
|
||||||
|
- workloads
|
||||||
+7
-7
@@ -4,15 +4,14 @@ metadata:
|
|||||||
name: platform-dev
|
name: platform-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
annotations:
|
annotations:
|
||||||
argocd.argoproj.io/sync-wave: "20"
|
argocd.argoproj.io/sync-wave: "10"
|
||||||
finalizers:
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
spec:
|
||||||
project: infra-dev
|
project: platform-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
|
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
path: infra/platform/overlays/dev
|
path: clusters/dev-k3s/manifests/auth-system
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: platform
|
namespace: platform
|
||||||
@@ -24,9 +23,10 @@ spec:
|
|||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
- CreateNamespace=true
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
+1
-4
@@ -1,8 +1,5 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
|
|
||||||
namespace: vault-transit
|
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- auth-system.yaml
|
||||||
- namespace.yaml
|
|
||||||
+6
-4
@@ -4,15 +4,16 @@ metadata:
|
|||||||
name: api-server-dev
|
name: api-server-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
annotations:
|
annotations:
|
||||||
argocd.argoproj.io/sync-wave: "40"
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
project: apps-dev
|
project: apps-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
|
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
path: apps/api-server/overlays/dev
|
path: clusters/dev-k3s/manifests/api-server
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: api-dev
|
namespace: api-dev
|
||||||
@@ -24,9 +25,10 @@ spec:
|
|||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
- CreateNamespace=true
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
+6
-4
@@ -4,15 +4,16 @@ metadata:
|
|||||||
name: auth-server-dev
|
name: auth-server-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
annotations:
|
annotations:
|
||||||
argocd.argoproj.io/sync-wave: "30"
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
project: apps-dev
|
project: apps-dev
|
||||||
source:
|
source:
|
||||||
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
|
repoURL: https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
targetRevision: main
|
targetRevision: main
|
||||||
path: apps/auth-server/overlays/dev
|
path: clusters/dev-k3s/manifests/auth-server
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: auth-dev
|
namespace: auth-dev
|
||||||
@@ -24,9 +25,10 @@ spec:
|
|||||||
syncOptions:
|
syncOptions:
|
||||||
- CreateNamespace=true
|
- CreateNamespace=true
|
||||||
- PruneLast=true
|
- PruneLast=true
|
||||||
- ApplyOutOfSyncOnly=true
|
- FailOnSharedResource=true
|
||||||
retry:
|
retry:
|
||||||
limit: 5
|
limit: 5
|
||||||
|
refresh: true
|
||||||
backoff:
|
backoff:
|
||||||
duration: 5s
|
duration: 5s
|
||||||
factor: 2
|
factor: 2
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- auth-server.yaml
|
||||||
|
- api-server.yaml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- projects
|
||||||
|
- applications
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
APP_SERVER_PORT=8082
|
||||||
|
APP_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://auth-public.auth-dev.svc.cluster.local
|
||||||
+1
@@ -3,6 +3,7 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
name: api-server
|
name: api-server
|
||||||
annotations:
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
+8
-3
@@ -4,16 +4,21 @@ kind: Kustomization
|
|||||||
namespace: api-dev
|
namespace: api-dev
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../../../workloads/api-server/base
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- configmap.yaml
|
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- public-access.yaml
|
- public-access.yaml
|
||||||
- networkpolicy.yaml
|
- networkpolicy.yaml
|
||||||
- ghcr-regcred.sealedsecret.yaml
|
- ghcr-regcred.sealedsecret.yaml
|
||||||
|
|
||||||
generatorOptions:
|
generatorOptions:
|
||||||
disableNameSuffixHash: true
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: api-server-config
|
||||||
|
envs:
|
||||||
|
- config.env
|
||||||
|
|
||||||
images:
|
images:
|
||||||
- name: ghcr.io/donghyeonka/project-api-server
|
- name: ghcr.io/donghyeonka/project-api-server
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
SPRING_PROFILES_ACTIVE=dev
|
||||||
|
SERVER_FORWARD_HEADERS_STRATEGY=framework
|
||||||
|
APP_DOCS_TITLE=Project Auth Server API
|
||||||
|
APP_DOCS_DESCRIPTION=dev auth-server OpenAPI
|
||||||
|
APP_DOCS_VERSION=v1
|
||||||
|
APP_DATASOURCE_URL=jdbc:postgresql://postgres.platform.svc.cluster.local:5432/project_auth
|
||||||
|
APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP=false
|
||||||
|
APP_SECURITY_OAUTH2_KEYCLOAK_ISSUER_URI=http://keycloak-public.platform.svc.cluster.local/realms/project-auth
|
||||||
|
APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_ID=project-auth-server
|
||||||
|
APP_SECURITY_OAUTH2_GOOGLE_REGISTRATION_ID=keycloak-google
|
||||||
|
APP_SECURITY_OAUTH2_GOOGLE_IDP_HINT=google
|
||||||
|
APP_SECURITY_OAUTH2_GITHUB_REGISTRATION_ID=keycloak-github
|
||||||
|
APP_SECURITY_OAUTH2_GITHUB_IDP_HINT=github
|
||||||
|
APP_SECURITY_JWT_ISSUER=http://auth-public.auth-dev.svc.cluster.local
|
||||||
|
APP_SECURITY_JWT_ACTIVE_KEY_ID=dev-vault-rsa-1
|
||||||
|
APP_SECURITY_JWT_GENERATE_KEY_PAIR_ON_STARTUP=false
|
||||||
|
APP_SECURITY_JWT_ACCESS_TOKEN_EXPIRATION=PT30M
|
||||||
|
APP_SECURITY_JWT_VAULT_ENABLED=true
|
||||||
|
APP_SECURITY_JWT_VAULT_ADDRESS=http://vault.vault.svc.cluster.local:8200
|
||||||
|
APP_SECURITY_JWT_VAULT_MOUNT_PATH=transit
|
||||||
|
APP_SECURITY_JWT_VAULT_TRANSIT_KEY_NAME=project-auth-jwt
|
||||||
+11
-1
@@ -7,6 +7,7 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-service-account-token-volume-name: vault-token
|
||||||
vault.hashicorp.com/agent-inject-secret-migration-env: database/creds/auth-db-migration-dev
|
vault.hashicorp.com/agent-inject-secret-migration-env: database/creds/auth-db-migration-dev
|
||||||
vault.hashicorp.com/agent-inject-template-migration-env: |
|
vault.hashicorp.com/agent-inject-template-migration-env: |
|
||||||
{{- with secret "database/creds/auth-db-migration-dev" -}}
|
{{- with secret "database/creds/auth-db-migration-dev" -}}
|
||||||
@@ -18,7 +19,16 @@ spec:
|
|||||||
vault.hashicorp.com/agent-run-as-user: "10001"
|
vault.hashicorp.com/agent-run-as-user: "10001"
|
||||||
vault.hashicorp.com/role: auth-db-migration-dev
|
vault.hashicorp.com/role: auth-db-migration-dev
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: true
|
automountServiceAccountToken: false
|
||||||
|
volumes:
|
||||||
|
- name: vault-token
|
||||||
|
projected:
|
||||||
|
defaultMode: 0444
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
audience: vault
|
||||||
|
expirationSeconds: 3600
|
||||||
|
path: token
|
||||||
containers:
|
containers:
|
||||||
- name: auth-db-migration
|
- name: auth-db-migration
|
||||||
command:
|
command:
|
||||||
+11
-1
@@ -8,6 +8,7 @@ spec:
|
|||||||
annotations:
|
annotations:
|
||||||
vault.hashicorp.com/agent-cache-enable: "true"
|
vault.hashicorp.com/agent-cache-enable: "true"
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-service-account-token-volume-name: vault-token
|
||||||
vault.hashicorp.com/agent-inject-secret-runtime-env: kv/data/dev/platform/postgres/auth-server
|
vault.hashicorp.com/agent-inject-secret-runtime-env: kv/data/dev/platform/postgres/auth-server
|
||||||
vault.hashicorp.com/agent-inject-template-runtime-env: |
|
vault.hashicorp.com/agent-inject-template-runtime-env: |
|
||||||
{{ with secret "kv/data/dev/platform/postgres/auth-server" }}
|
{{ with secret "kv/data/dev/platform/postgres/auth-server" }}
|
||||||
@@ -23,7 +24,16 @@ spec:
|
|||||||
vault.hashicorp.com/agent-run-as-user: "10001"
|
vault.hashicorp.com/agent-run-as-user: "10001"
|
||||||
vault.hashicorp.com/role: auth-server-dev
|
vault.hashicorp.com/role: auth-server-dev
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: true
|
automountServiceAccountToken: false
|
||||||
|
volumes:
|
||||||
|
- name: vault-token
|
||||||
|
projected:
|
||||||
|
defaultMode: 0444
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
audience: vault
|
||||||
|
expirationSeconds: 3600
|
||||||
|
path: token
|
||||||
containers:
|
containers:
|
||||||
- name: auth-server
|
- name: auth-server
|
||||||
command:
|
command:
|
||||||
+1
@@ -3,6 +3,7 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
name: auth-server
|
name: auth-server
|
||||||
annotations:
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
+11
-6
@@ -4,9 +4,8 @@ kind: Kustomization
|
|||||||
namespace: auth-dev
|
namespace: auth-dev
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../../../workloads/auth-server/base
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- configmap.yaml
|
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- public-access.yaml
|
- public-access.yaml
|
||||||
- networkpolicy.yaml
|
- networkpolicy.yaml
|
||||||
@@ -17,9 +16,15 @@ patches:
|
|||||||
- path: db-migration-job.vault-patch.yaml
|
- path: db-migration-job.vault-patch.yaml
|
||||||
|
|
||||||
generatorOptions:
|
generatorOptions:
|
||||||
disableNameSuffixHash: true
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: auth-server-config
|
||||||
|
envs:
|
||||||
|
- config.env
|
||||||
|
|
||||||
images:
|
images:
|
||||||
- name: ghcr.io/donghyeonka/project-auth-server
|
- name: ghcr.io/donghyeonka/project-auth-server
|
||||||
newName: ghcr.io/donghyeonka/project-auth-server
|
newName: ghcr.io/donghyeonka/project-auth-server
|
||||||
newTag: 1f47f2c
|
newTag: 1f47f2c
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
POSTGRES_SUPERUSER=postgres
|
||||||
|
POSTGRES_DEFAULT_DB=postgres
|
||||||
|
AUTH_DB_NAME=project_auth
|
||||||
|
AUTH_DB_USER=project_auth
|
||||||
|
KEYCLOAK_DB_NAME=keycloak
|
||||||
|
KEYCLOAK_DB_USER=keycloak
|
||||||
|
KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME=admin
|
||||||
|
KEYCLOAK_CLIENT_ID=project-auth-server
|
||||||
|
AUTH_SERVER_BASE_URL=http://auth-public.auth-dev.svc.cluster.local
|
||||||
+22
-6
@@ -7,6 +7,7 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-service-account-token-volume-name: vault-token
|
||||||
vault.hashicorp.com/agent-inject-perms-keycloak-sync-env: "0644"
|
vault.hashicorp.com/agent-inject-perms-keycloak-sync-env: "0644"
|
||||||
vault.hashicorp.com/agent-inject-secret-keycloak-sync-env: kv/data/dev/platform/keycloak/bootstrap-admin
|
vault.hashicorp.com/agent-inject-secret-keycloak-sync-env: kv/data/dev/platform/keycloak/bootstrap-admin
|
||||||
vault.hashicorp.com/agent-inject-template-keycloak-sync-env: |
|
vault.hashicorp.com/agent-inject-template-keycloak-sync-env: |
|
||||||
@@ -20,7 +21,16 @@ spec:
|
|||||||
vault.hashicorp.com/agent-pre-populate-only: "true"
|
vault.hashicorp.com/agent-pre-populate-only: "true"
|
||||||
vault.hashicorp.com/role: keycloak-client-sync-dev
|
vault.hashicorp.com/role: keycloak-client-sync-dev
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: true
|
automountServiceAccountToken: false
|
||||||
|
volumes:
|
||||||
|
- name: vault-token
|
||||||
|
projected:
|
||||||
|
defaultMode: 0444
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
audience: vault
|
||||||
|
expirationSeconds: 3600
|
||||||
|
path: token
|
||||||
containers:
|
containers:
|
||||||
- name: keycloak-client-sync
|
- name: keycloak-client-sync
|
||||||
command:
|
command:
|
||||||
@@ -31,13 +41,19 @@ spec:
|
|||||||
. /vault/secrets/keycloak-sync-env
|
. /vault/secrets/keycloak-sync-env
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
until /opt/keycloak/bin/kcadm.sh config credentials \
|
ready=false
|
||||||
--server http://keycloak.platform.svc.cluster.local \
|
for _ in $(seq 1 60); do
|
||||||
--realm master \
|
if /opt/keycloak/bin/kcadm.sh config credentials \
|
||||||
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
|
--server http://keycloak.platform.svc.cluster.local \
|
||||||
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD" >/dev/null 2>&1; do
|
--realm master \
|
||||||
|
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
|
||||||
|
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD" >/dev/null 2>&1; then
|
||||||
|
ready=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
|
test "$ready" = true
|
||||||
|
|
||||||
CLIENT_UUID=$(/opt/keycloak/bin/kcadm.sh get clients \
|
CLIENT_UUID=$(/opt/keycloak/bin/kcadm.sh get clients \
|
||||||
-r project-auth \
|
-r project-auth \
|
||||||
+11
-1
@@ -7,6 +7,7 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-service-account-token-volume-name: vault-token
|
||||||
vault.hashicorp.com/agent-inject-perms-keycloak-env: "0644"
|
vault.hashicorp.com/agent-inject-perms-keycloak-env: "0644"
|
||||||
vault.hashicorp.com/agent-inject-secret-keycloak-env: kv/data/dev/platform/postgres/keycloak
|
vault.hashicorp.com/agent-inject-secret-keycloak-env: kv/data/dev/platform/postgres/keycloak
|
||||||
vault.hashicorp.com/agent-inject-template-keycloak-env: |
|
vault.hashicorp.com/agent-inject-template-keycloak-env: |
|
||||||
@@ -19,7 +20,16 @@ spec:
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
vault.hashicorp.com/role: keycloak-dev
|
vault.hashicorp.com/role: keycloak-dev
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: true
|
automountServiceAccountToken: false
|
||||||
|
volumes:
|
||||||
|
- name: vault-token
|
||||||
|
projected:
|
||||||
|
defaultMode: 0444
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
audience: vault
|
||||||
|
expirationSeconds: 3600
|
||||||
|
path: token
|
||||||
containers:
|
containers:
|
||||||
- name: keycloak
|
- name: keycloak
|
||||||
command:
|
command:
|
||||||
+10
-2
@@ -4,13 +4,21 @@ kind: Kustomization
|
|||||||
namespace: platform
|
namespace: platform
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../../../platform/auth-system/base
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- configmap.yaml
|
|
||||||
- keycloak-ingress.yaml
|
- keycloak-ingress.yaml
|
||||||
- public-access.yaml
|
- public-access.yaml
|
||||||
- networkpolicy.yaml
|
- networkpolicy.yaml
|
||||||
|
|
||||||
|
generatorOptions:
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: platform-config
|
||||||
|
envs:
|
||||||
|
- config.env
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: postgres.vault-patch.yaml
|
- path: postgres.vault-patch.yaml
|
||||||
- path: keycloak.vault-patch.yaml
|
- path: keycloak.vault-patch.yaml
|
||||||
+2
@@ -2,6 +2,8 @@ apiVersion: v1
|
|||||||
kind: Namespace
|
kind: Namespace
|
||||||
metadata:
|
metadata:
|
||||||
name: platform
|
name: platform
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-options: Prune=false
|
||||||
labels:
|
labels:
|
||||||
vault-injection: enabled
|
vault-injection: enabled
|
||||||
pod-security.kubernetes.io/enforce: baseline
|
pod-security.kubernetes.io/enforce: baseline
|
||||||
+11
-1
@@ -7,6 +7,7 @@ spec:
|
|||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
vault.hashicorp.com/agent-inject: "true"
|
vault.hashicorp.com/agent-inject: "true"
|
||||||
|
vault.hashicorp.com/agent-service-account-token-volume-name: vault-token
|
||||||
vault.hashicorp.com/agent-inject-perms-postgres-env: "0644"
|
vault.hashicorp.com/agent-inject-perms-postgres-env: "0644"
|
||||||
vault.hashicorp.com/agent-inject-secret-postgres-env: kv/data/dev/platform/postgres/superuser
|
vault.hashicorp.com/agent-inject-secret-postgres-env: kv/data/dev/platform/postgres/superuser
|
||||||
vault.hashicorp.com/agent-inject-template-postgres-env: |
|
vault.hashicorp.com/agent-inject-template-postgres-env: |
|
||||||
@@ -23,7 +24,16 @@ spec:
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
vault.hashicorp.com/role: postgres-dev
|
vault.hashicorp.com/role: postgres-dev
|
||||||
spec:
|
spec:
|
||||||
automountServiceAccountToken: true
|
automountServiceAccountToken: false
|
||||||
|
volumes:
|
||||||
|
- name: vault-token
|
||||||
|
projected:
|
||||||
|
defaultMode: 0444
|
||||||
|
sources:
|
||||||
|
- serviceAccountToken:
|
||||||
|
audience: vault
|
||||||
|
expirationSeconds: 3600
|
||||||
|
path: token
|
||||||
containers:
|
containers:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
command:
|
command:
|
||||||
+1
-1
@@ -4,6 +4,6 @@ kind: Kustomization
|
|||||||
namespace: vault
|
namespace: vault
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../../../platform/security/vault/base
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- networkpolicy.yaml
|
- networkpolicy.yaml
|
||||||
+2
@@ -2,6 +2,8 @@ apiVersion: v1
|
|||||||
kind: Namespace
|
kind: Namespace
|
||||||
metadata:
|
metadata:
|
||||||
name: vault
|
name: vault
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-options: Prune=false
|
||||||
labels:
|
labels:
|
||||||
pod-security.kubernetes.io/enforce: baseline
|
pod-security.kubernetes.io/enforce: baseline
|
||||||
pod-security.kubernetes.io/enforce-version: latest
|
pod-security.kubernetes.io/enforce-version: latest
|
||||||
-22
@@ -36,28 +36,6 @@ spec:
|
|||||||
---
|
---
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: NetworkPolicy
|
kind: NetworkPolicy
|
||||||
metadata:
|
|
||||||
name: vault-server-allow-transit-egress
|
|
||||||
spec:
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app: vault
|
|
||||||
policyTypes:
|
|
||||||
- Egress
|
|
||||||
egress:
|
|
||||||
- to:
|
|
||||||
- namespaceSelector:
|
|
||||||
matchLabels:
|
|
||||||
kubernetes.io/metadata.name: vault-transit
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app: vault-transit
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 8200
|
|
||||||
---
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
metadata:
|
||||||
name: vault-server-allow-postgres-egress
|
name: vault-server-allow-postgres-egress
|
||||||
spec:
|
spec:
|
||||||
@@ -3,12 +3,15 @@ kind: AppProject
|
|||||||
metadata:
|
metadata:
|
||||||
name: apps-dev
|
name: apps-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "-10"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
description: Dev application workloads managed by Argo CD
|
description: Dev application workloads managed by Argo CD
|
||||||
sourceRepos:
|
sourceRepos:
|
||||||
- https://github.com/DongHyeonka/Project-Auth-GitOps
|
- https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
destinations:
|
destinations:
|
||||||
- namespace: auth-dev
|
- namespace: auth-dev
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
@@ -20,31 +23,19 @@ spec:
|
|||||||
namespaceResourceWhitelist:
|
namespaceResourceWhitelist:
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
- group: ""
|
|
||||||
kind: Secret
|
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: Service
|
kind: Service
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
- group: ""
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
- group: "bitnami.com"
|
- group: "bitnami.com"
|
||||||
kind: SealedSecret
|
kind: SealedSecret
|
||||||
- group: "apps"
|
- group: "apps"
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
- group: "apps"
|
|
||||||
kind: StatefulSet
|
|
||||||
- group: "apps"
|
|
||||||
kind: ReplicaSet
|
|
||||||
- group: "autoscaling"
|
|
||||||
kind: HorizontalPodAutoscaler
|
|
||||||
- group: "batch"
|
- group: "batch"
|
||||||
kind: Job
|
kind: Job
|
||||||
- group: "networking.k8s.io"
|
- group: "networking.k8s.io"
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
- group: "networking.k8s.io"
|
- group: "networking.k8s.io"
|
||||||
kind: NetworkPolicy
|
kind: NetworkPolicy
|
||||||
- group: "policy"
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
orphanedResources:
|
orphanedResources:
|
||||||
warn: true
|
warn: true
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: AppProject
|
||||||
|
metadata:
|
||||||
|
name: cluster-addons-dev
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "-10"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
|
finalizers:
|
||||||
|
- resources-finalizer.argocd.argoproj.io
|
||||||
|
spec:
|
||||||
|
description: Cluster-scoped controllers required by the dev GitOps stack
|
||||||
|
sourceRepos:
|
||||||
|
- https://bitnami.github.io/sealed-secrets
|
||||||
|
- https://helm.releases.hashicorp.com
|
||||||
|
destinations:
|
||||||
|
- namespace: kube-system
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
- namespace: vault
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
clusterResourceWhitelist:
|
||||||
|
- group: ""
|
||||||
|
kind: Namespace
|
||||||
|
- group: apiextensions.k8s.io
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
- group: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
- group: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
- group: admissionregistration.k8s.io
|
||||||
|
kind: MutatingWebhookConfiguration
|
||||||
|
namespaceResourceWhitelist:
|
||||||
|
- group: ""
|
||||||
|
kind: ConfigMap
|
||||||
|
- group: ""
|
||||||
|
kind: Secret
|
||||||
|
- group: ""
|
||||||
|
kind: Service
|
||||||
|
- group: ""
|
||||||
|
kind: ServiceAccount
|
||||||
|
- group: apps
|
||||||
|
kind: Deployment
|
||||||
|
- group: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
- group: rbac.authorization.k8s.io
|
||||||
|
kind: RoleBinding
|
||||||
|
orphanedResources:
|
||||||
|
warn: true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- apps.yaml
|
||||||
|
- cluster-addons.yaml
|
||||||
|
- platform.yaml
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
apiVersion: argoproj.io/v1alpha1
|
||||||
kind: AppProject
|
kind: AppProject
|
||||||
metadata:
|
metadata:
|
||||||
name: infra-prod
|
name: platform-dev
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "-10"
|
||||||
|
argocd.argoproj.io/sync-options: Prune=confirm,Delete=confirm
|
||||||
finalizers:
|
finalizers:
|
||||||
- resources-finalizer.argocd.argoproj.io
|
- resources-finalizer.argocd.argoproj.io
|
||||||
spec:
|
spec:
|
||||||
description: Prod shared infrastructure managed by Argo CD
|
description: Dev shared infrastructure managed by Argo CD
|
||||||
sourceRepos:
|
sourceRepos:
|
||||||
- https://github.com/DongHyeonka/Project-Auth-GitOps
|
- https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops
|
||||||
- https://bitnami-labs.github.io/sealed-secrets
|
|
||||||
destinations:
|
destinations:
|
||||||
- namespace: platform-prod
|
- namespace: platform
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
- namespace: kube-system
|
- namespace: vault
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
clusterResourceWhitelist:
|
clusterResourceWhitelist:
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
- group: "apiextensions.k8s.io"
|
|
||||||
kind: CustomResourceDefinition
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
- group: "rbac.authorization.k8s.io"
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
- group: "rbac.authorization.k8s.io"
|
- group: "rbac.authorization.k8s.io"
|
||||||
@@ -27,16 +27,12 @@ spec:
|
|||||||
namespaceResourceWhitelist:
|
namespaceResourceWhitelist:
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
- group: ""
|
|
||||||
kind: Secret
|
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: Service
|
kind: Service
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
- group: ""
|
- group: ""
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
- group: "bitnami.com"
|
|
||||||
kind: SealedSecret
|
|
||||||
- group: "rbac.authorization.k8s.io"
|
- group: "rbac.authorization.k8s.io"
|
||||||
kind: Role
|
kind: Role
|
||||||
- group: "rbac.authorization.k8s.io"
|
- group: "rbac.authorization.k8s.io"
|
||||||
@@ -45,17 +41,11 @@ spec:
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
- group: "apps"
|
- group: "apps"
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
- group: "apps"
|
|
||||||
kind: ReplicaSet
|
|
||||||
- group: "autoscaling"
|
|
||||||
kind: HorizontalPodAutoscaler
|
|
||||||
- group: "batch"
|
- group: "batch"
|
||||||
kind: Job
|
kind: Job
|
||||||
- group: "networking.k8s.io"
|
- group: "networking.k8s.io"
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
- group: "networking.k8s.io"
|
- group: "networking.k8s.io"
|
||||||
kind: NetworkPolicy
|
kind: NetworkPolicy
|
||||||
- group: "policy"
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
orphanedResources:
|
orphanedResources:
|
||||||
warn: true
|
warn: true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# ADR 0001: Internal Gitea is canonical
|
||||||
|
|
||||||
|
Status: accepted
|
||||||
|
|
||||||
|
The internal repository
|
||||||
|
`https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops` is the only
|
||||||
|
writable deployment source.
|
||||||
|
|
||||||
|
Argo CD and Gitea Actions use this URL. A GitHub copy may exist only as a
|
||||||
|
read-only mirror with monitored replication; it must never be an independent
|
||||||
|
deployment branch. GHCR remains an image registry and does not require GitHub
|
||||||
|
to host the GitOps source.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# ADR 0002: Terraform state ownership
|
||||||
|
|
||||||
|
Status: accepted
|
||||||
|
|
||||||
|
Terraform은 VM/네트워크뿐 아니라 provider가 제공되는 Vault API 객체도
|
||||||
|
관리할 수 있다. 현재 저장소의 Terraform 범위는 Vault API이고 실제
|
||||||
|
machine provisioning은 provider가 확정될 때 별도 root로 추가한다.
|
||||||
|
|
||||||
|
`dev-k3s`는 두 state만 사용한다.
|
||||||
|
|
||||||
|
- `vault-core`: mounts, auth backends, policies, Kubernetes/JWT roles,
|
||||||
|
application Transit key
|
||||||
|
- `vault-database`: PostgreSQL connection과 dynamic roles
|
||||||
|
|
||||||
|
resource/API path 하나는 한 state에만 속한다. state는 암호화, versioning,
|
||||||
|
access control, locking이 가능한 remote backend에 저장한다.
|
||||||
|
|
||||||
|
`vault-core`는 privilege-escalation 가능한 객체를 포함하므로 제한된
|
||||||
|
관리자 실행만 허용한다. `vault-database`는 core가 생성한
|
||||||
|
`vault-database-automation-dev` 정책의 short-lived identity로 실행한다.
|
||||||
|
|
||||||
|
Secret payload는 Terraform resource/data source로 관리하지 않는다.
|
||||||
|
필수 credential은 ephemeral variable과 provider write-only argument를
|
||||||
|
통해서만 apply에 전달한다.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# ADR 0003: Vault topology
|
||||||
|
|
||||||
|
Status: accepted for dev, production decision pending
|
||||||
|
|
||||||
|
동일한 단일 노드 K3s 안의 두 Vault는 failure domain을 분리하지 못하면서
|
||||||
|
초기화, Transit credential, rotation과 staged apply 절차를 추가했다.
|
||||||
|
따라서 `dev-k3s`는 단일 self-hosted Vault로 단순화한다.
|
||||||
|
|
||||||
|
Dev profile:
|
||||||
|
|
||||||
|
- single-node integrated Raft
|
||||||
|
- Shamir 1-of-1 init/unseal
|
||||||
|
- TLS 미적용
|
||||||
|
- 명시적 backup/recovery runbook
|
||||||
|
|
||||||
|
이 구성은 production에 사용할 수 없다. production은 다음 중 하나를
|
||||||
|
선택해야 한다.
|
||||||
|
|
||||||
|
- managed Vault
|
||||||
|
- workload cluster 밖의 독립 HA Vault
|
||||||
|
- 최소 3-node integrated-Raft + TLS + KMS/HSM auto-unseal + PDB,
|
||||||
|
anti-affinity와 정기 restore exercise
|
||||||
|
|
||||||
|
production Vault가 결정되기 전에는 production manifest와 Terraform root를
|
||||||
|
만들지 않는다.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# ADR 0004: Single Argo CD root
|
||||||
|
|
||||||
|
Status: accepted
|
||||||
|
|
||||||
|
Argo CD 설치 후 `bootstrap/argocd/root-application.yaml` 하나만 seed한다.
|
||||||
|
root는 `clusters/dev-k3s`의 AppProject와 모든 child Application을 소유한다.
|
||||||
|
|
||||||
|
반복 `kubectl apply`와 foundation/platform/application별 root wrapper는
|
||||||
|
제거한다. routine deployment는 Git merge만으로 시작한다.
|
||||||
|
|
||||||
|
Child Application의 sync wave는 객체 생성 순서를 가독성 있게 표현하지만
|
||||||
|
서로 다른 Application의 readiness dependency로 간주하지 않는다.
|
||||||
|
Workload와 hook은 Vault/DB가 늦게 준비되는 상황을 retry할 수 있어야 한다.
|
||||||
|
|
||||||
|
Root가 child Application을 prune하거나 삭제하려면 확인이 필요하다.
|
||||||
|
shared resource 소유권 충돌은 sync를 실패시킨다. 현재 규모에서는 명시적
|
||||||
|
Application을 사용하고 두 번째 클러스터가 생길 때 ApplicationSet을
|
||||||
|
검토한다.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# ADR 0005: Cluster-first repository layout
|
||||||
|
|
||||||
|
Status: accepted
|
||||||
|
|
||||||
|
현재는 하나의 platform 팀, 하나의 dev cluster와 소수 workload를 가지므로
|
||||||
|
GitOps configuration monorepo를 유지한다. application source repository와
|
||||||
|
deployment configuration repository는 분리한다.
|
||||||
|
|
||||||
|
- `platform/`, `workloads/`: 환경 중립 base
|
||||||
|
- `clusters/<cluster>/manifests`: cluster-specific final composition
|
||||||
|
- `clusters/<cluster>/applications`: Argo reconciliation inventory
|
||||||
|
- `iac/terraform`: Kubernetes manifest와 분리된 external API IaC
|
||||||
|
- `bootstrap`: controller가 존재하기 전의 최소 seed
|
||||||
|
|
||||||
|
production 접근권한, 소유 팀, Terraform backend 또는 release cadence가
|
||||||
|
실제로 갈라질 때 platform GitOps, workload GitOps, IaC repo 분리를
|
||||||
|
재검토한다. 존재하지 않는 환경의 skeleton은 유지하지 않는다.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# ADR 0006: Gateway API first, Istio deferred
|
||||||
|
|
||||||
|
Status: accepted
|
||||||
|
|
||||||
|
현재 단일 노드 K3s와 auth/api 중심 workload에는 service mesh 운영 비용을
|
||||||
|
정당화할 mTLS identity, L7 authorization, canary traffic policy 또는
|
||||||
|
multi-team 요구가 없다. 이번 개편에는 Istio를 설치하지 않는다.
|
||||||
|
|
||||||
|
선행 작업:
|
||||||
|
|
||||||
|
1. Traefik Gateway API provider와 GatewayClass 검증
|
||||||
|
2. Ingress를 Gateway/HTTPRoute로 이관
|
||||||
|
3. north-south TLS
|
||||||
|
4. 내부 호출의 ingress hairpin 제거
|
||||||
|
5. Vault/PostgreSQL native TLS
|
||||||
|
6. NetworkPolicy regression test와 observability/SLO
|
||||||
|
|
||||||
|
Istio 요구가 실제화되면 sidecar가 아니라 ambient mode로 제한 pilot한다.
|
||||||
|
초기 범위는 api-server와 auth-server이며 Vault, Vault injector,
|
||||||
|
PostgreSQL은 제외한다. ztunnel L4부터 시작하고 L7 정책이 필요할 때만
|
||||||
|
waypoint를 추가한다.
|
||||||
|
|
||||||
|
다음 기능 요구 중 두 개 이상과 운영 선행조건이 모두 충족될 때 ADR을
|
||||||
|
재검토한다.
|
||||||
|
|
||||||
|
- ServiceAccount identity 기반 east-west mTLS
|
||||||
|
- path/JWT 기반 L7 authorization
|
||||||
|
- canary traffic split/retry/timeout/outlier detection
|
||||||
|
- 지속적인 서비스·namespace·팀 증가
|
||||||
|
- application instrumentation만으로 해결하기 어려운 장애 분석
|
||||||
|
|
||||||
|
현재 Ingress를 즉시 제거하지 않는다. TLS, DNS, GatewayClass 계약이
|
||||||
|
확정되기 전 가상의 Gateway 설정을 배포하지 않기 위함이다.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Argo CD layout
|
||||||
|
|
||||||
|
`bootstrap/argocd/root-application.yaml`이 유일한 수동 seed입니다. 이
|
||||||
|
Application은 `clusters/dev-k3s`를 source로 사용하고 다음 리소스를
|
||||||
|
소유합니다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
clusters/dev-k3s
|
||||||
|
├── projects
|
||||||
|
└── applications
|
||||||
|
├── foundation
|
||||||
|
│ ├── sealed-secrets
|
||||||
|
│ ├── vault
|
||||||
|
│ └── vault-agent-injector
|
||||||
|
├── platform
|
||||||
|
│ └── auth-system
|
||||||
|
└── workloads
|
||||||
|
├── auth-server
|
||||||
|
└── api-server
|
||||||
|
```
|
||||||
|
|
||||||
|
AppProject는 root sync wave `-10`, foundation은 `0~1`, platform은 `10`,
|
||||||
|
workload는 `20`입니다. 이 wave는 child Application 객체 생성 순서만
|
||||||
|
표현하며 서로 다른 Application의 readiness dependency로 사용하지
|
||||||
|
않습니다. Vault Agent와 workload는 필요한 Vault/DB API가 준비될 때까지
|
||||||
|
자체 retry 가능한 형태여야 합니다.
|
||||||
|
|
||||||
|
모든 child Application은 auto-sync, prune, self-heal을 사용합니다.
|
||||||
|
Application 삭제와 parent prune은 확인이 필요하며, shared resource
|
||||||
|
소유권 충돌은 `FailOnSharedResource=true`로 실패시킵니다.
|
||||||
|
|
||||||
|
Sync hook이 있는 `auth-server`와 `auth-system`에는 selective sync 옵션을
|
||||||
|
사용하지 않습니다. DB migration과 Keycloak client sync는 같은
|
||||||
|
Application 내부 wave로 순서를 제어합니다.
|
||||||
|
|
||||||
|
클러스터가 하나이고 child Application 수가 적으므로 현재는 명시적
|
||||||
|
Application을 사용합니다. 두 번째 클러스터나 실제 production이 생길
|
||||||
|
때 foundation/platform/workload별 ApplicationSet 도입을 검토합니다.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Deployment architecture
|
||||||
|
|
||||||
|
## Reconciliation boundaries
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gitea main
|
||||||
|
|
|
||||||
|
+-- Argo CD root -> AppProjects + child Applications -> Kubernetes
|
||||||
|
|
|
||||||
|
+-- approved Terraform runner -> Vault API
|
||||||
|
```
|
||||||
|
|
||||||
|
Argo CD는 Kubernetes desired state만 관리합니다. 최초 Argo 설치/root
|
||||||
|
seed와 문서화된 recovery 외에는 직접 cluster mutation을 하지 않습니다.
|
||||||
|
Terraform은 Config Management Plugin이나 Argo hook 안에서 실행하지
|
||||||
|
않습니다.
|
||||||
|
|
||||||
|
## Kustomize ownership
|
||||||
|
|
||||||
|
- `platform/`, `workloads/`: 환경 중립 base
|
||||||
|
- `clusters/dev-k3s/manifests/`: namespace, host, image, Vault role 및
|
||||||
|
NetworkPolicy를 포함하는 최종 cluster composition
|
||||||
|
- Argo CD Application: final composition만 source로 사용
|
||||||
|
|
||||||
|
지원하지 않는 production overlay는 존재하지 않습니다. production
|
||||||
|
계약과 승인 경계가 확정될 때 별도로 생성합니다.
|
||||||
|
|
||||||
|
## In-application ordering
|
||||||
|
|
||||||
|
`auth-server`의 한 sync operation 안에서:
|
||||||
|
|
||||||
|
- generated ConfigMap과 일반 리소스: wave `0`
|
||||||
|
- database migration Sync hook: wave `5`
|
||||||
|
- Deployment: wave `10`
|
||||||
|
- north-south route: wave `20`
|
||||||
|
|
||||||
|
`auth-system`의 Keycloak client sync도 idempotent Sync hook이며 deadline,
|
||||||
|
backoff, `BeforeHookCreation,HookSucceeded` cleanup을 사용합니다.
|
||||||
|
|
||||||
|
## Stateful lifecycle
|
||||||
|
|
||||||
|
Vault와 PostgreSQL PVC는 `Prune=false`로 보호합니다. child Application
|
||||||
|
prune/delete는 확인이 필요합니다. path 이동이나 Application rename 전에는
|
||||||
|
새 owner가 동일 live resource를 정상적으로 추적하는지 확인한 후 이전
|
||||||
|
owner를 non-cascading 방식으로 제거합니다.
|
||||||
|
|
||||||
|
## Image promotion
|
||||||
|
|
||||||
|
첫-party image는 애플리케이션 CI가 얻은 정확한 GHCR digest를 Gitea
|
||||||
|
workflow에 전달합니다. workflow는 digest 변경 PR을 만들고, validation과
|
||||||
|
승인을 거쳐 merge된 뒤 Argo CD가 배포합니다.
|
||||||
|
|
||||||
|
현재 short-SHA tag는 migration 시점의 예외입니다. private GHCR을 읽을
|
||||||
|
자격증명이 이 저장소 실행 환경에 없으므로 임의 digest로 바꾸지 않았고,
|
||||||
|
다음 정상 promotion에서 `digest:`로 교체됩니다.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Secret trust boundaries
|
||||||
|
|
||||||
|
## Dev Vault
|
||||||
|
|
||||||
|
`dev-k3s`는 단일 self-hosted Vault를 사용합니다. 동일 workload
|
||||||
|
클러스터에 별도의 Transit Vault를 두지 않습니다. 단일 Vault는 다음을
|
||||||
|
소유합니다.
|
||||||
|
|
||||||
|
- KV-v2 runtime secret path
|
||||||
|
- Kubernetes auth와 workload role
|
||||||
|
- dynamic PostgreSQL credential
|
||||||
|
- 애플리케이션 JWT signing용 Transit key
|
||||||
|
|
||||||
|
dev Vault는 Shamir 1-of-1로 한 번 초기화하고 재시작 시 명시적으로
|
||||||
|
unseal합니다. 이 방식은 개발 환경 전용입니다. production에서는 managed
|
||||||
|
Vault 또는 독립 failure domain의 HA integrated-Raft와 KMS/HSM
|
||||||
|
auto-unseal을 사용해야 합니다.
|
||||||
|
|
||||||
|
## Terraform
|
||||||
|
|
||||||
|
`vault-core` state는 mounts, auth, policies, roles와 JWT key를 소유하며
|
||||||
|
제한된 관리자만 적용합니다. `vault-database`는 PostgreSQL connection과
|
||||||
|
dynamic roles만 소유하고 `vault-database-automation-dev` 정책을 사용합니다.
|
||||||
|
|
||||||
|
Terraform variable로 전달되는 token과 PostgreSQL password는 ephemeral/
|
||||||
|
write-only 경계를 사용합니다. KV payload는 Terraform resource/data
|
||||||
|
source로 읽거나 쓰지 않습니다.
|
||||||
|
|
||||||
|
## Workload authentication
|
||||||
|
|
||||||
|
workload는 audience `vault`, TTL 1시간의 projected ServiceAccount token으로
|
||||||
|
Vault Kubernetes auth에 로그인합니다. token은 Vault Agent가 사용하며
|
||||||
|
application container에 Kubernetes bearer token을 직접 노출하지 않습니다.
|
||||||
|
|
||||||
|
Secret payload는 승인된 운영자가 Vault에 직접 기록합니다. 값은 Git,
|
||||||
|
Gitea Actions log, Terraform state, Kubernetes manifest에 남기지 않습니다.
|
||||||
|
|
||||||
|
## Bootstrap material
|
||||||
|
|
||||||
|
Vault init output은 기본적으로 `.local/vault/dev-k3s-init.json`에 mode
|
||||||
|
`0600`으로 생성됩니다. encrypted custody로 이동한 후 working copy를
|
||||||
|
제거합니다. initial root token은 `vault-core`와 operator auth 검증 직후
|
||||||
|
폐기합니다.
|
||||||
|
|
||||||
|
Sealed Secrets는 private GHCR pull credential에만 사용합니다. controller
|
||||||
|
private key는 별도 복구 저장소에 백업해야 합니다.
|
||||||
|
|
||||||
|
## Dev limitations
|
||||||
|
|
||||||
|
- Vault, PostgreSQL, ingress가 아직 TLS를 사용하지 않음
|
||||||
|
- single-node Vault와 PostgreSQL
|
||||||
|
- Kubernetes API egress CIDR가 현재 dev cluster에 종속
|
||||||
|
- 정적 bootstrap secret은 coordinated rotation 필요
|
||||||
|
|
||||||
|
이 제약은 production에서 허용되지 않습니다.
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
# Archived documentation
|
||||||
|
|
||||||
|
Files in this directory describe historical repository states. Paths,
|
||||||
|
workflows, credentials and operational commands may no longer exist.
|
||||||
|
|
||||||
|
Do not execute archived procedures. Use the root README and `docs/runbooks/`.
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# Bootstrap an empty dev-k3s cluster
|
||||||
|
|
||||||
|
이 runbook은 폐기 가능한 개발 클러스터만 대상으로 합니다. production에
|
||||||
|
사용하지 않습니다.
|
||||||
|
|
||||||
|
## 1. Preflight
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl config current-context
|
||||||
|
kubectl cluster-info
|
||||||
|
make validate
|
||||||
|
```
|
||||||
|
|
||||||
|
의도한 dev cluster가 아니면 중단합니다. 내부 Gitea가 private이면 Argo CD가
|
||||||
|
root repository를 읽을 수 있는 read-only credential을 외부 secret
|
||||||
|
authority에서 먼저 provision해야 합니다. credential은 이 저장소에
|
||||||
|
commit하지 않습니다.
|
||||||
|
|
||||||
|
remote state backend 파일을 준비합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p .local/terraform-backend/dev-k3s
|
||||||
|
cp iac/terraform/backend/dev-k3s/vault-core.s3.hcl.example \
|
||||||
|
.local/terraform-backend/dev-k3s/vault-core.s3.hcl
|
||||||
|
cp iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example \
|
||||||
|
.local/terraform-backend/dev-k3s/vault-database.s3.hcl
|
||||||
|
```
|
||||||
|
|
||||||
|
실제 bucket, endpoint와 workload identity를 설정합니다. backend credential은
|
||||||
|
파일에 넣지 않습니다.
|
||||||
|
|
||||||
|
## 2. Argo CD와 root Application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make bootstrap KUBE_CONTEXT="$(kubectl config current-context)"
|
||||||
|
kubectl -n argocd get application project-gitops-dev-k3s
|
||||||
|
```
|
||||||
|
|
||||||
|
이 명령이 수행하는 직접 cluster mutation은 Argo CD 설치와 root seed뿐입니다.
|
||||||
|
Child Application은 root가 생성합니다.
|
||||||
|
|
||||||
|
## 3. Dev Vault 초기화
|
||||||
|
|
||||||
|
Vault Pod가 생성될 때까지 기다린 뒤 operator workstation에서 forward합니다.
|
||||||
|
이 port-forward는 최초 dev bootstrap용이며 routine runner 모델이 아닙니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n vault wait --for=create pod -l app=vault --timeout=300s
|
||||||
|
kubectl -n vault port-forward deployment/vault 8200:8200
|
||||||
|
```
|
||||||
|
|
||||||
|
별도 terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VAULT_ADDR=http://127.0.0.1:8200
|
||||||
|
./hack/vault-init.sh init
|
||||||
|
```
|
||||||
|
|
||||||
|
`.local/vault/dev-k3s-init.json`을 즉시 encrypted custody로 복사합니다.
|
||||||
|
dev-only 1-of-1 unseal key와 initial root token이 있으므로 일반 backup과
|
||||||
|
분리합니다.
|
||||||
|
|
||||||
|
## 4. Vault core
|
||||||
|
|
||||||
|
초기 root token을 shell history에 직접 적지 않습니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TF_VAR_vault_addr="$VAULT_ADDR"
|
||||||
|
export TF_VAR_vault_token="$(
|
||||||
|
jq -r '.root_token' .local/vault/dev-k3s-init.json
|
||||||
|
)"
|
||||||
|
|
||||||
|
make terraform-plan \
|
||||||
|
TF_ROOT=vault-core \
|
||||||
|
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl
|
||||||
|
|
||||||
|
make terraform-apply \
|
||||||
|
TF_ROOT=vault-core \
|
||||||
|
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl \
|
||||||
|
APPROVE_APPLY=dev-k3s/vault-core
|
||||||
|
```
|
||||||
|
|
||||||
|
plan에서 mount, auth backend, policy, role, JWT Transit key 이외 객체가
|
||||||
|
나오면 apply하지 않습니다.
|
||||||
|
|
||||||
|
## 5. Runtime secret seed
|
||||||
|
|
||||||
|
Secret 값은 Git/Terraform을 통과하지 않습니다. 아래 변수는 terminal
|
||||||
|
session에만 유지합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -r -s -p "PostgreSQL superuser password: " POSTGRES_SUPERUSER_PASSWORD
|
||||||
|
echo
|
||||||
|
read -r -s -p "Auth database password: " AUTH_DB_PASSWORD
|
||||||
|
echo
|
||||||
|
read -r -s -p "Keycloak database password: " KEYCLOAK_DB_PASSWORD
|
||||||
|
echo
|
||||||
|
read -r -s -p "Keycloak bootstrap admin password: " KEYCLOAK_ADMIN_PASSWORD
|
||||||
|
echo
|
||||||
|
read -r -s -p "Auth-server Keycloak client secret: " KEYCLOAK_CLIENT_SECRET
|
||||||
|
echo
|
||||||
|
|
||||||
|
secret_file="$(mktemp)"
|
||||||
|
trap 'rm -f "$secret_file"' EXIT
|
||||||
|
chmod 0600 "$secret_file"
|
||||||
|
|
||||||
|
jq -n --arg password "$POSTGRES_SUPERUSER_PASSWORD" \
|
||||||
|
'{POSTGRES_SUPERUSER_PASSWORD: $password}' >"$secret_file"
|
||||||
|
vault kv put kv/dev/platform/postgres/superuser @"$secret_file"
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--arg password "$AUTH_DB_PASSWORD" \
|
||||||
|
'{AUTH_DB_PASSWORD: $password, APP_DATASOURCE_USERNAME: "project_auth", APP_DATASOURCE_PASSWORD: $password}' >"$secret_file"
|
||||||
|
vault kv put kv/dev/platform/postgres/auth-server @"$secret_file"
|
||||||
|
|
||||||
|
jq -n --arg password "$KEYCLOAK_DB_PASSWORD" \
|
||||||
|
'{KEYCLOAK_DB_PASSWORD: $password}' >"$secret_file"
|
||||||
|
vault kv put kv/dev/platform/postgres/keycloak @"$secret_file"
|
||||||
|
|
||||||
|
jq -n --arg password "$KEYCLOAK_ADMIN_PASSWORD" \
|
||||||
|
'{KC_BOOTSTRAP_ADMIN_PASSWORD: $password}' >"$secret_file"
|
||||||
|
vault kv put kv/dev/platform/keycloak/bootstrap-admin @"$secret_file"
|
||||||
|
|
||||||
|
jq -n --arg secret "$KEYCLOAK_CLIENT_SECRET" \
|
||||||
|
'{KEYCLOAK_CLIENT_SECRET: $secret, APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET: $secret}' >"$secret_file"
|
||||||
|
vault kv put kv/dev/platform/keycloak/client-auth-server @"$secret_file"
|
||||||
|
|
||||||
|
rm -f "$secret_file"
|
||||||
|
trap - EXIT
|
||||||
|
```
|
||||||
|
|
||||||
|
PostgreSQL이 Vault Agent 주입 후 시작하는지 확인합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n platform rollout status statefulset/postgres --timeout=600s
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Vault database state
|
||||||
|
|
||||||
|
초기 root token으로 TTL이 짧은 database 전용 token을 발급합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TF_VAR_vault_token="$(
|
||||||
|
vault token create \
|
||||||
|
-policy=vault-database-automation-dev \
|
||||||
|
-ttl=30m \
|
||||||
|
-format=json |
|
||||||
|
jq -r '.auth.client_token'
|
||||||
|
)"
|
||||||
|
export TF_VAR_postgres_admin_password="$POSTGRES_SUPERUSER_PASSWORD"
|
||||||
|
export TF_VAR_postgres_admin_password_version=1
|
||||||
|
|
||||||
|
make terraform-plan \
|
||||||
|
TF_ROOT=vault-database \
|
||||||
|
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl
|
||||||
|
|
||||||
|
make terraform-apply \
|
||||||
|
TF_ROOT=vault-database \
|
||||||
|
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl \
|
||||||
|
APPROVE_APPLY=dev-k3s/vault-database
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Root token 폐기
|
||||||
|
|
||||||
|
Kubernetes auth operator login이 동작하는지 먼저 검증합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
operator_jwt="$(
|
||||||
|
kubectl -n vault create token vault-operator \
|
||||||
|
--audience=vault \
|
||||||
|
--duration=10m
|
||||||
|
)"
|
||||||
|
operator_token="$(
|
||||||
|
VAULT_TOKEN= vault write \
|
||||||
|
-format=json \
|
||||||
|
auth/kubernetes/login \
|
||||||
|
role=vault-operator-dev \
|
||||||
|
jwt="$operator_jwt" |
|
||||||
|
jq -r '.auth.client_token'
|
||||||
|
)"
|
||||||
|
VAULT_TOKEN="$operator_token" vault token lookup >/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
검증 후 initial root token을 폐기합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./hack/vault-init.sh revoke-root
|
||||||
|
unset operator_jwt operator_token
|
||||||
|
unset TF_VAR_vault_token TF_VAR_postgres_admin_password
|
||||||
|
unset POSTGRES_SUPERUSER_PASSWORD AUTH_DB_PASSWORD KEYCLOAK_DB_PASSWORD
|
||||||
|
unset KEYCLOAK_ADMIN_PASSWORD KEYCLOAK_CLIENT_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
encrypted custody로 옮긴 init material의 local working copy는 조직의
|
||||||
|
dev recovery 정책에 따라 제거합니다.
|
||||||
|
|
||||||
|
## 8. 확인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n argocd get applications
|
||||||
|
kubectl -n vault get pods
|
||||||
|
kubectl -n platform get pods
|
||||||
|
kubectl -n auth-dev get pods
|
||||||
|
kubectl -n api-dev get pods
|
||||||
|
```
|
||||||
|
|
||||||
|
모든 Application의 sync/health를 확인하고 DB migration 및 Keycloak client
|
||||||
|
sync hook 결과를 검토합니다. 실패한 hook을 고치기 위해 child manifest를
|
||||||
|
직접 apply하지 말고 Git PR을 사용합니다.
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Sealed Secrets backup and recovery
|
||||||
|
|
||||||
|
Existing SealedSecrets are decryptable only with a controller private key.
|
||||||
|
Back up every key after first install and after rotation.
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
Use an encrypted operator workstation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
umask 077
|
||||||
|
kubectl -n kube-system get secret \
|
||||||
|
-l sealedsecrets.bitnami.com/sealed-secrets-key \
|
||||||
|
-o json > .local/sealed-secrets-keys.json
|
||||||
|
chmod 0600 .local/sealed-secrets-keys.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Encrypt the file with the organization's recovery mechanism, store at least
|
||||||
|
two independently controlled copies, and delete the plaintext working copy.
|
||||||
|
Record cluster, date and checksum without recording private key data.
|
||||||
|
|
||||||
|
## Restore
|
||||||
|
|
||||||
|
Restore keys before application SealedSecrets sync:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n kube-system apply -f .local/sealed-secrets-keys.json
|
||||||
|
kubectl -n kube-system rollout restart deployment/sealed-secrets-controller
|
||||||
|
kubectl -n kube-system rollout status deployment/sealed-secrets-controller
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify both `auth-dev/ghcr-regcred` and `api-dev/ghcr-regcred` are created.
|
||||||
|
|
||||||
|
If no key backup exists, old ciphertext cannot be recovered. Create a new
|
||||||
|
controller key and reseal every Secret from the original credential source.
|
||||||
|
|
||||||
|
## Rotation
|
||||||
|
|
||||||
|
The chart requests periodic key renewal. Old keys must remain until all
|
||||||
|
ciphertext has been resealed and verified. GHCR credential rotation requires:
|
||||||
|
|
||||||
|
1. issue an organization-owned read-only package credential;
|
||||||
|
2. create namespace-scoped SealedSecrets with the current controller cert;
|
||||||
|
3. merge and verify image pulls;
|
||||||
|
4. revoke the previous credential;
|
||||||
|
5. update the encrypted key backup.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Terraform v2 state migration
|
||||||
|
|
||||||
|
새 클러스터에는 이 runbook이 필요하지 않습니다. legacy local state 또는
|
||||||
|
이전 `provider-foundation`, `workload-foundation`, `workload-config`,
|
||||||
|
`database-config` remote state가 실제로 존재할 때만 수행합니다.
|
||||||
|
|
||||||
|
State 이동은 live object 삭제보다 위험할 수 있습니다. maintenance window와
|
||||||
|
독립 backup 없이 진행하지 않습니다.
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
| 이전 state | 목표 |
|
||||||
|
|---|---|
|
||||||
|
| provider Transit Vault state | archive 후 provider Vault 폐기 절차에서 별도 처리 |
|
||||||
|
| workload foundation | `vault-core`의 기준 state |
|
||||||
|
| workload config | 소유 객체를 `vault-core`로 이동 |
|
||||||
|
| database config | `vault-database`로 backend key migration |
|
||||||
|
|
||||||
|
동일 클러스터 Transit Vault는 더 이상 desired state가 아닙니다. Terraform
|
||||||
|
state에서 먼저 삭제하거나 destroy하지 않습니다. snapshot과 seal dependency
|
||||||
|
해제 확인 후 별도 decommission 승인을 받아 처리합니다.
|
||||||
|
|
||||||
|
## 1. Inventory와 backup
|
||||||
|
|
||||||
|
모든 operator machine, runner, remote backend에서 state 위치를 확인합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find . -type f \
|
||||||
|
\( -name 'terraform.tfstate*' -o -name '*.tfplan' \) \
|
||||||
|
-not -path './.git/*'
|
||||||
|
```
|
||||||
|
|
||||||
|
각 state를 `terraform state pull`로 encrypted offline custody에 저장하고
|
||||||
|
checksum을 기록합니다. backup에는 secret data가 포함될 수 있습니다.
|
||||||
|
|
||||||
|
## 2. Backend key migration
|
||||||
|
|
||||||
|
`workload-foundation` backend에 연결한 상태에서 새 `vault-core` backend
|
||||||
|
configuration으로 `terraform init -migrate-state`를 수행합니다.
|
||||||
|
`database-config`도 같은 방식으로 `vault-database` key로 이동합니다.
|
||||||
|
|
||||||
|
실제 backend 파일과 이전 key는 환경마다 다르므로 명령에 값을 하드코딩하지
|
||||||
|
않습니다. migration 전후 `terraform state pull` checksum과 `state list`를
|
||||||
|
비교합니다.
|
||||||
|
|
||||||
|
## 3. Workload configuration ownership 이동
|
||||||
|
|
||||||
|
이전 `workload-config` state의 다음 객체를 `vault-core` state의 선언된
|
||||||
|
address로 이동합니다.
|
||||||
|
|
||||||
|
- application Vault policies
|
||||||
|
- workload Kubernetes auth roles
|
||||||
|
|
||||||
|
`terraform state mv -state=<source-backup> -state-out=<target-working-copy>`를
|
||||||
|
사용해 offline copy에서 먼저 연습합니다. target address는 현재
|
||||||
|
`module.workload_policies`와 `module.workload_roles`의 `terraform state list`
|
||||||
|
결과를 기준으로 합니다. resource 이름을 추측하지 않습니다.
|
||||||
|
|
||||||
|
이동 후 두 state 모두 plan합니다.
|
||||||
|
|
||||||
|
- `vault-core`: 변경 없음 또는 address-only 이동
|
||||||
|
- legacy workload-config: 삭제할 live object 없음
|
||||||
|
|
||||||
|
두 plan 중 하나라도 destroy를 제안하면 중단하고 backup state를 복원합니다.
|
||||||
|
|
||||||
|
## 4. Database state
|
||||||
|
|
||||||
|
기존 database state가 없고 live Vault 객체만 존재할 때만 다음 import ID를
|
||||||
|
사용합니다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
vault_database_secret_backend_connection.platform_postgres database/config/platform-postgres-dev
|
||||||
|
vault_database_secret_backend_role.auth_db_migration database/roles/auth-db-migration-dev
|
||||||
|
vault_database_secret_backend_role.postgres_operator database/roles/postgres-operator-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
이미 다른 state에 address가 있으면 import하지 말고 state ownership을 먼저
|
||||||
|
이동합니다.
|
||||||
|
|
||||||
|
## 5. Cutover 완료 조건
|
||||||
|
|
||||||
|
- 두 목표 state가 remote backend와 locking을 사용
|
||||||
|
- 동일 Vault path가 두 state list에 나타나지 않음
|
||||||
|
- plan에 예상하지 않은 create/delete가 없음
|
||||||
|
- legacy state와 backup은 immutable archive
|
||||||
|
- repo와 runner에 local state/provider directory가 없음
|
||||||
|
|
||||||
|
검증이 끝나기 전 legacy backend를 삭제하지 않습니다.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Vault backup and recovery
|
||||||
|
|
||||||
|
`dev-k3s` Vault는 single-node integrated Raft입니다. upgrade, Terraform
|
||||||
|
state migration, storage 작업 전에 snapshot을 생성합니다.
|
||||||
|
|
||||||
|
## Snapshot
|
||||||
|
|
||||||
|
short-lived authorized token과 Vault API 연결을 준비합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
umask 077
|
||||||
|
vault operator raft snapshot save .local/vault/dev-k3s.snap
|
||||||
|
sha256sum .local/vault/dev-k3s.snap
|
||||||
|
chmod 0600 .local/vault/dev-k3s.snap
|
||||||
|
```
|
||||||
|
|
||||||
|
즉시 암호화해 init/unseal material과 다른 위치에 보관합니다. snapshot에는
|
||||||
|
secret payload가 포함됩니다.
|
||||||
|
|
||||||
|
## Recovery material
|
||||||
|
|
||||||
|
- Raft snapshot
|
||||||
|
- unseal key
|
||||||
|
- 현재 manifest revision
|
||||||
|
- Terraform remote state backup
|
||||||
|
- Sealed Secrets controller key
|
||||||
|
|
||||||
|
Initial root token은 recovery material이 아닙니다. bootstrap 완료 후
|
||||||
|
폐기해야 합니다.
|
||||||
|
|
||||||
|
## Restore exercise
|
||||||
|
|
||||||
|
분기마다 isolated disposable cluster에서 다음을 검증합니다.
|
||||||
|
|
||||||
|
1. 동일 Vault version과 storage 설정 배포
|
||||||
|
2. Vault init 후 snapshot restore
|
||||||
|
3. 복구 key로 unseal
|
||||||
|
4. Kubernetes auth 재연결 확인
|
||||||
|
5. KV read, JWT signing, dynamic database lease issue/revoke 확인
|
||||||
|
6. recovery time과 누락된 의존성 기록
|
||||||
|
|
||||||
|
현재 dev 구성은 production recovery 설계가 아닙니다. production은
|
||||||
|
independent HA Vault, TLS, auto-unseal과 더 엄격한 backup custody가
|
||||||
|
필요합니다.
|
||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 --context <kube-context>" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" != "--context" || -z "${2:-}" || -n "${3:-}" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected_context="$2"
|
||||||
|
|
||||||
|
for cmd in curl kubectl sha256sum; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "$cmd is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
current_context="$(kubectl config current-context)"
|
||||||
|
if [[ "$current_context" != "$expected_context" ]]; then
|
||||||
|
echo "Refusing bootstrap: current context is ${current_context}, expected ${expected_context}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "${REPO_ROOT}/bootstrap/argocd/version.env"
|
||||||
|
|
||||||
|
manifest="$(mktemp)"
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$manifest"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
curl -fsSL \
|
||||||
|
"https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml" \
|
||||||
|
-o "$manifest"
|
||||||
|
printf '%s %s\n' "$ARGOCD_INSTALL_SHA256" "$manifest" | sha256sum -c -
|
||||||
|
|
||||||
|
kubectl --context "$expected_context" create namespace argocd --dry-run=client -o yaml |
|
||||||
|
kubectl --context "$expected_context" apply -f -
|
||||||
|
kubectl --context "$expected_context" apply \
|
||||||
|
--server-side \
|
||||||
|
--force-conflicts \
|
||||||
|
-n argocd \
|
||||||
|
-f "$manifest"
|
||||||
|
kubectl --context "$expected_context" -n argocd \
|
||||||
|
rollout status deployment/argocd-server --timeout=300s
|
||||||
|
kubectl --context "$expected_context" -n argocd \
|
||||||
|
rollout status deployment/argocd-repo-server --timeout=300s
|
||||||
|
kubectl --context "$expected_context" apply \
|
||||||
|
-f "${REPO_ROOT}/bootstrap/argocd/root-application.yaml"
|
||||||
|
|
||||||
|
echo "Argo CD ${ARGOCD_VERSION} and the dev-k3s root Application are installed."
|
||||||
Executable
+138
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
for cmd in bash git helm jq kubectl rg terraform; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "$cmd is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
forbidden_files="$(
|
||||||
|
git ls-files |
|
||||||
|
rg '(^|/)(\\.terraform|\\.terraform-state|\\.local)(/|$)|(^|/)terraform\\.tfstate($|\\.)|\\.tfplan$' ||
|
||||||
|
true
|
||||||
|
)"
|
||||||
|
if [[ -n "$forbidden_files" ]]; then
|
||||||
|
echo "Generated or state files are tracked:" >&2
|
||||||
|
echo "$forbidden_files" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r script; do
|
||||||
|
bash -n "$script"
|
||||||
|
done < <(rg --files -g '*.sh')
|
||||||
|
|
||||||
|
while IFS= read -r script; do
|
||||||
|
if [[ ! -x "$script" ]]; then
|
||||||
|
echo "Shell entry point is not executable: ${script}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done < <(rg --files hack -g '*.sh')
|
||||||
|
|
||||||
|
jq empty platform/auth-system/base/files/keycloak/project-auth-realm.json
|
||||||
|
|
||||||
|
overlays=(
|
||||||
|
clusters/dev-k3s
|
||||||
|
clusters/dev-k3s/manifests/api-server
|
||||||
|
clusters/dev-k3s/manifests/auth-server
|
||||||
|
clusters/dev-k3s/manifests/auth-system
|
||||||
|
clusters/dev-k3s/manifests/vault
|
||||||
|
)
|
||||||
|
for overlay in "${overlays[@]}"; do
|
||||||
|
kubectl kustomize "$overlay" >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
sealed_secrets_render="$(
|
||||||
|
helm template sealed-secrets sealed-secrets \
|
||||||
|
--repo https://bitnami.github.io/sealed-secrets \
|
||||||
|
--version 2.17.9 \
|
||||||
|
--namespace kube-system \
|
||||||
|
--set fullnameOverride=sealed-secrets-controller \
|
||||||
|
--set image.repository=bitnami/sealed-secrets-controller \
|
||||||
|
--set-string image.tag=0.33.1@sha256:e7fad65c2d2f47e48d9ca17408ed56961bfa6a6dd74ccd4a1a214664156534bc
|
||||||
|
)"
|
||||||
|
if [[ "$sealed_secrets_render" != *'image: docker.io/bitnami/sealed-secrets-controller:0.33.1@sha256:e7fad65c2d2f47e48d9ca17408ed56961bfa6a6dd74ccd4a1a214664156534bc'* ]]; then
|
||||||
|
echo "Sealed Secrets image digest was not rendered by the Helm chart." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
vault_injector_render="$(
|
||||||
|
helm template vault-agent-injector vault \
|
||||||
|
--repo https://helm.releases.hashicorp.com \
|
||||||
|
--version 0.32.0 \
|
||||||
|
--namespace vault \
|
||||||
|
--set server.enabled=false \
|
||||||
|
--set injector.enabled=true \
|
||||||
|
--set global.externalVaultAddr=http://vault.vault.svc.cluster.local:8200 \
|
||||||
|
--set global.tlsDisable=true \
|
||||||
|
--set injector.image.repository=hashicorp/vault-k8s \
|
||||||
|
--set-string injector.image.tag=1.7.2@sha256:ae3d307658b72a1cf35dab9bdf92c995d45cdc7183af0516857714b5bd0ba84d \
|
||||||
|
--set injector.agentImage.repository=hashicorp/vault \
|
||||||
|
--set-string injector.agentImage.tag=1.18.5@sha256:750bb37c1638fa194ab37053a81618c61bb0491ddec6fccac87c07a8e6cd8166
|
||||||
|
)"
|
||||||
|
if [[ "$vault_injector_render" != *'image: "hashicorp/vault-k8s:1.7.2@sha256:ae3d307658b72a1cf35dab9bdf92c995d45cdc7183af0516857714b5bd0ba84d"'* ||
|
||||||
|
"$vault_injector_render" != *'value: "hashicorp/vault:1.18.5@sha256:750bb37c1638fa194ab37053a81618c61bb0491ddec6fccac87c07a8e6cd8166"'* ]]; then
|
||||||
|
echo "Vault injector or Agent image digest was not rendered by the Helm chart." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rendered_images="$(
|
||||||
|
{
|
||||||
|
kubectl kustomize clusters/dev-k3s/manifests/auth-system
|
||||||
|
kubectl kustomize clusters/dev-k3s/manifests/vault
|
||||||
|
} | rg '^[[:space:]]+image: (hashicorp/vault|postgres|quay\\.io/keycloak)'
|
||||||
|
)"
|
||||||
|
if printf '%s\n' "$rendered_images" | rg -v '@sha256:[a-f0-9]{64}$'; then
|
||||||
|
echo "A third-party runtime image is not pinned by digest." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
terraform fmt -check -recursive iac/terraform
|
||||||
|
terraform_roots=(
|
||||||
|
iac/terraform/live/dev-k3s/vault-core
|
||||||
|
iac/terraform/live/dev-k3s/vault-database
|
||||||
|
)
|
||||||
|
for root in "${terraform_roots[@]}"; do
|
||||||
|
data_dir="$(mktemp -d)"
|
||||||
|
TF_DATA_DIR="$data_dir" terraform -chdir="$root" init \
|
||||||
|
-backend=false \
|
||||||
|
-input=false \
|
||||||
|
-lockfile=readonly >/dev/null
|
||||||
|
TF_DATA_DIR="$data_dir" terraform -chdir="$root" validate
|
||||||
|
rm -rf "$data_dir"
|
||||||
|
done
|
||||||
|
|
||||||
|
if rg -n \
|
||||||
|
'github\\.com/DongHyeonka/Project-Auth-GitOps|bitnami-labs\\.github\\.io/sealed-secrets|/home/donghyeon/dev/Project-Auth-GitOps|terraform/vault(-transit)?/(dev|reconcile)' \
|
||||||
|
--glob '!docs/archive/**' \
|
||||||
|
--glob '!policies/legacy/**' \
|
||||||
|
--glob '!hack/validate.sh' \
|
||||||
|
.; then
|
||||||
|
echo "Current files contain a legacy repository URL, Helm URL, absolute path, or Terraform root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if rg -n 'uses:[[:space:]]+[^#[:space:]]+@v[0-9]' .gitea/workflows; then
|
||||||
|
echo "Gitea Actions must be pinned to an immutable commit SHA." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if rg -n 'ApplyOutOfSyncOnly=true' bootstrap clusters; then
|
||||||
|
echo "ApplyOutOfSyncOnly is incompatible with hook-based migrations and must not be enabled." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if rg -n 'git[[:space:]]+push[^#]*(HEAD:)?main([[:space:]]|$)' .gitea hack; then
|
||||||
|
echo "Automation must promote changes through a branch and review, not push directly to main." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git diff --check
|
||||||
|
echo "Repository validation passed."
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:8200}"
|
||||||
|
VAULT_INIT_OUTPUT="${VAULT_INIT_OUTPUT:-${REPO_ROOT}/.local/vault/dev-k3s-init.json}"
|
||||||
|
|
||||||
|
for cmd in jq vault; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "$cmd is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
export VAULT_ADDR
|
||||||
|
|
||||||
|
status_json() {
|
||||||
|
local output=""
|
||||||
|
local status_code=0
|
||||||
|
|
||||||
|
set +e
|
||||||
|
output="$(vault status -format=json 2>/dev/null)"
|
||||||
|
status_code=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ "$status_code" -ne 0 && "$status_code" -ne 2 ]]; then
|
||||||
|
echo "Vault is not reachable at ${VAULT_ADDR}." >&2
|
||||||
|
return "$status_code"
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
unseal() {
|
||||||
|
local status=""
|
||||||
|
local unseal_key=""
|
||||||
|
|
||||||
|
status="$(status_json)"
|
||||||
|
if [[ "$(jq -r '.sealed' <<<"$status")" == "false" ]]; then
|
||||||
|
echo "Vault is already unsealed."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$VAULT_INIT_OUTPUT" ]]; then
|
||||||
|
echo "Init material is unavailable: ${VAULT_INIT_OUTPUT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unseal_key="$(jq -er '.unseal_keys_b64[0]' "$VAULT_INIT_OUTPUT")"
|
||||||
|
vault operator unseal "$unseal_key" >/dev/null
|
||||||
|
echo "Vault is unsealed."
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
local status=""
|
||||||
|
|
||||||
|
status="$(status_json)"
|
||||||
|
if [[ "$(jq -r '.initialized' <<<"$status")" == "true" ]]; then
|
||||||
|
echo "Refusing initialization: Vault is already initialized." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -e "$VAULT_INIT_OUTPUT" ]]; then
|
||||||
|
echo "Refusing to overwrite existing init material: ${VAULT_INIT_OUTPUT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
mkdir -p "$(dirname "$VAULT_INIT_OUTPUT")"
|
||||||
|
vault operator init \
|
||||||
|
-key-shares=1 \
|
||||||
|
-key-threshold=1 \
|
||||||
|
-format=json >"$VAULT_INIT_OUTPUT"
|
||||||
|
chmod 0600 "$VAULT_INIT_OUTPUT"
|
||||||
|
unseal
|
||||||
|
|
||||||
|
echo "Dev Vault was initialized with a dev-only 1-of-1 Shamir key."
|
||||||
|
echo "Move ${VAULT_INIT_OUTPUT} to encrypted custody before continuing."
|
||||||
|
}
|
||||||
|
|
||||||
|
revoke_root() {
|
||||||
|
local root_token=""
|
||||||
|
local temporary=""
|
||||||
|
|
||||||
|
if [[ ! -f "$VAULT_INIT_OUTPUT" ]]; then
|
||||||
|
echo "Init material is unavailable: ${VAULT_INIT_OUTPUT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
root_token="$(jq -er '.root_token' "$VAULT_INIT_OUTPUT")"
|
||||||
|
VAULT_TOKEN="$root_token" vault token revoke -self
|
||||||
|
|
||||||
|
temporary="$(mktemp "${VAULT_INIT_OUTPUT}.XXXXXX")"
|
||||||
|
jq 'del(.root_token)' "$VAULT_INIT_OUTPUT" >"$temporary"
|
||||||
|
chmod 0600 "$temporary"
|
||||||
|
mv "$temporary" "$VAULT_INIT_OUTPUT"
|
||||||
|
echo "The initial root token was revoked and removed from the local init file."
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
init)
|
||||||
|
init
|
||||||
|
;;
|
||||||
|
unseal)
|
||||||
|
unseal
|
||||||
|
;;
|
||||||
|
revoke-root)
|
||||||
|
revoke_root
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 <init|unseal|revoke-root>" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
bucket = "project-gitops-terraform-state"
|
||||||
|
key = "dev-k3s/vault-core.tfstate"
|
||||||
|
region = "us-east-1"
|
||||||
|
encrypt = true
|
||||||
|
use_lockfile = true
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
bucket = "project-gitops-terraform-state"
|
||||||
|
key = "dev-k3s/vault-database.tfstate"
|
||||||
|
region = "us-east-1"
|
||||||
|
encrypt = true
|
||||||
|
use_lockfile = true
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/vault" {
|
||||||
|
version = "5.7.0"
|
||||||
|
constraints = "~> 5.7.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:Pm0AcUSYmBPZgRahQX/ahiYcjtZODSAEc2rK8r8MQ18=",
|
||||||
|
"zh:1dd9ab6d23f61a5e522efcb462f1fd6f4a210c77b9038c8e12fa5fa663b45d01",
|
||||||
|
"zh:3c98d37ead857c980f7b9285f8c3e1eb7a8fd6d6799275c311c6997973389cc9",
|
||||||
|
"zh:3df895fbaed383e3748ba1b50f5f1046f75503483bc3d783992059f85c85ba31",
|
||||||
|
"zh:3e9faaa0a85c6f03c7fd7f8b7008bb3fbb8777f26c001875947cafa47f91c657",
|
||||||
|
"zh:52a057d0c6cde7cbfd9ceb78a3781dcfc81cf108c533f454530ea6bb87a9bea8",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:8521c3825254a5f7fbff8f42ca57cabf052366f0420f5f239ebebf8292c03d0e",
|
||||||
|
"zh:953563d429e40087eb34faf22f28e781e50eee27cfc9ac1ad04308ba592a647f",
|
||||||
|
"zh:a52dd76bb7f5b86cb8de7380d2e68b47ec4445782c16ee205e6a013be35a57b6",
|
||||||
|
"zh:bdad38c95a14c8cce1eeadcc539cf9bf74902ce7c662b79105ad993bb48ec073",
|
||||||
|
"zh:d3c676d7d12c15b58518fa3ee7fc398a13893b4057fe9bf4bc1fe635f3fb995a",
|
||||||
|
"zh:f8673b6c06da80e912c9e32dd4853f07bfca386968d5b33c9fceb6f68b519959",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.11.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "~> 5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backend "s3" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "vault" {
|
||||||
|
address = var.vault_addr
|
||||||
|
skip_child_token = true
|
||||||
|
token = var.vault_token
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
policy_dir = "${path.module}/../../../../../policies/vault/dev-k3s"
|
||||||
|
|
||||||
|
workload_policies = {
|
||||||
|
auth-server-dev = file("${local.policy_dir}/auth-server-dev.hcl")
|
||||||
|
auth-db-migration-dev = file("${local.policy_dir}/auth-db-migration-dev.hcl")
|
||||||
|
postgres-dev = file("${local.policy_dir}/postgres-dev.hcl")
|
||||||
|
keycloak-dev = file("${local.policy_dir}/keycloak-dev.hcl")
|
||||||
|
keycloak-client-sync-dev = file("${local.policy_dir}/keycloak-client-sync-dev.hcl")
|
||||||
|
postgres-operator-dev = file("${local.policy_dir}/postgres-operator-dev.hcl")
|
||||||
|
keycloak-operator-dev = file("${local.policy_dir}/keycloak-operator-dev.hcl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_mount" "kv" {
|
||||||
|
path = var.kv_mount_path
|
||||||
|
type = "kv"
|
||||||
|
options = {
|
||||||
|
version = "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_mount" "database" {
|
||||||
|
path = var.database_mount_path
|
||||||
|
type = "database"
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_mount" "transit" {
|
||||||
|
path = var.transit_mount_path
|
||||||
|
type = "transit"
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_auth_backend" "kubernetes" {
|
||||||
|
path = var.kubernetes_auth_path
|
||||||
|
type = "kubernetes"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_kubernetes_auth_backend_config" "cluster" {
|
||||||
|
backend = vault_auth_backend.kubernetes.path
|
||||||
|
disable_iss_validation = true
|
||||||
|
disable_local_ca_jwt = false
|
||||||
|
kubernetes_host = var.kubernetes_host
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_transit_secret_backend_key" "project_auth_jwt" {
|
||||||
|
backend = vault_mount.transit.path
|
||||||
|
name = var.jwt_transit_key_name
|
||||||
|
type = "rsa-2048"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_policy" "platform_admin" {
|
||||||
|
name = var.platform_admin_policy_name
|
||||||
|
policy = file("${local.policy_dir}/platform-admin-dev.hcl")
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_policy" "database_automation" {
|
||||||
|
name = var.database_automation_policy_name
|
||||||
|
policy = file("${local.policy_dir}/vault-database-automation-dev.hcl")
|
||||||
|
}
|
||||||
|
|
||||||
|
module "workload_policies" {
|
||||||
|
source = "../../../modules/vault-policy-set"
|
||||||
|
|
||||||
|
policies = local.workload_policies
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_kubernetes_auth_backend_role" "operator" {
|
||||||
|
audience = var.kubernetes_token_audience
|
||||||
|
backend = vault_auth_backend.kubernetes.path
|
||||||
|
bound_service_account_names = [var.operator_service_account_name]
|
||||||
|
bound_service_account_namespaces = [var.operator_service_account_namespace]
|
||||||
|
role_name = var.operator_role_name
|
||||||
|
token_policies = [vault_policy.platform_admin.name]
|
||||||
|
token_ttl = var.operator_token_ttl_seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
module "workload_roles" {
|
||||||
|
source = "../../../modules/vault-kubernetes-roles"
|
||||||
|
|
||||||
|
backend = vault_auth_backend.kubernetes.path
|
||||||
|
roles = {
|
||||||
|
auth-server-dev = {
|
||||||
|
audiences = [var.kubernetes_token_audience]
|
||||||
|
service_account_names = ["auth-server"]
|
||||||
|
service_account_namespaces = ["auth-dev"]
|
||||||
|
token_policies = [module.workload_policies.names["auth-server-dev"]]
|
||||||
|
token_ttl = var.kubernetes_role_ttl_seconds
|
||||||
|
}
|
||||||
|
auth-db-migration-dev = {
|
||||||
|
audiences = [var.kubernetes_token_audience]
|
||||||
|
service_account_names = ["auth-db-migration"]
|
||||||
|
service_account_namespaces = ["auth-dev"]
|
||||||
|
token_policies = [module.workload_policies.names["auth-db-migration-dev"]]
|
||||||
|
token_ttl = var.kubernetes_role_ttl_seconds
|
||||||
|
}
|
||||||
|
postgres-dev = {
|
||||||
|
audiences = [var.kubernetes_token_audience]
|
||||||
|
service_account_names = ["postgres"]
|
||||||
|
service_account_namespaces = ["platform"]
|
||||||
|
token_policies = [module.workload_policies.names["postgres-dev"]]
|
||||||
|
token_ttl = var.kubernetes_role_ttl_seconds
|
||||||
|
}
|
||||||
|
keycloak-dev = {
|
||||||
|
audiences = [var.kubernetes_token_audience]
|
||||||
|
service_account_names = ["keycloak"]
|
||||||
|
service_account_namespaces = ["platform"]
|
||||||
|
token_policies = [module.workload_policies.names["keycloak-dev"]]
|
||||||
|
token_ttl = var.kubernetes_role_ttl_seconds
|
||||||
|
}
|
||||||
|
keycloak-client-sync-dev = {
|
||||||
|
audiences = [var.kubernetes_token_audience]
|
||||||
|
service_account_names = ["keycloak-client-sync"]
|
||||||
|
service_account_namespaces = ["platform"]
|
||||||
|
token_policies = [module.workload_policies.names["keycloak-client-sync-dev"]]
|
||||||
|
token_ttl = var.kubernetes_role_ttl_seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_jwt_auth_backend" "ci" {
|
||||||
|
count = var.ci_jwt_oidc_discovery_url == null ? 0 : 1
|
||||||
|
|
||||||
|
bound_issuer = var.ci_jwt_bound_issuer
|
||||||
|
oidc_discovery_url = var.ci_jwt_oidc_discovery_url
|
||||||
|
path = var.ci_jwt_auth_path
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_jwt_auth_backend_role" "ci" {
|
||||||
|
count = var.ci_jwt_oidc_discovery_url == null ? 0 : 1
|
||||||
|
|
||||||
|
backend = vault_jwt_auth_backend.ci[0].path
|
||||||
|
bound_audiences = var.ci_jwt_bound_audiences
|
||||||
|
bound_claims = var.ci_jwt_bound_claims
|
||||||
|
bound_claims_type = "glob"
|
||||||
|
role_name = var.ci_jwt_role_name
|
||||||
|
role_type = "jwt"
|
||||||
|
token_explicit_max_ttl = var.ci_token_ttl_seconds
|
||||||
|
token_policies = [vault_policy.database_automation.name]
|
||||||
|
user_claim = var.ci_jwt_user_claim
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
variable "database_automation_policy_name" {
|
||||||
|
description = "Least-privilege policy used by the approved Vault database runner."
|
||||||
|
type = string
|
||||||
|
default = "vault-database-automation-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_auth_path" {
|
||||||
|
description = "JWT auth mount used by external CI."
|
||||||
|
type = string
|
||||||
|
default = "jwt-ci"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_bound_audiences" {
|
||||||
|
description = "Accepted CI JWT audiences."
|
||||||
|
type = set(string)
|
||||||
|
default = []
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_bound_claims" {
|
||||||
|
description = "Claims that bind CI JWTs to the canonical repository and protected branch."
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_bound_issuer" {
|
||||||
|
description = "Expected issuer for CI JWTs."
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
nullable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_oidc_discovery_url" {
|
||||||
|
description = "CI OIDC discovery URL. Null keeps JWT auth disabled until the issuer is confirmed."
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
nullable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_role_name" {
|
||||||
|
description = "Vault role used by the GitOps configuration workflow."
|
||||||
|
type = string
|
||||||
|
default = "project-gitops-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_jwt_user_claim" {
|
||||||
|
description = "JWT claim used as the Vault identity alias."
|
||||||
|
type = string
|
||||||
|
default = "sub"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ci_token_ttl_seconds" {
|
||||||
|
description = "Maximum lifetime for a CI Vault token."
|
||||||
|
type = number
|
||||||
|
default = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "database_mount_path" {
|
||||||
|
description = "Workload Vault database secrets mount path."
|
||||||
|
type = string
|
||||||
|
default = "database"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "jwt_transit_key_name" {
|
||||||
|
description = "Transit key used for application JWT signing."
|
||||||
|
type = string
|
||||||
|
default = "project-auth-jwt"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "kubernetes_auth_path" {
|
||||||
|
description = "Kubernetes auth backend path."
|
||||||
|
type = string
|
||||||
|
default = "kubernetes"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "kubernetes_host" {
|
||||||
|
description = "Kubernetes TokenReview API address."
|
||||||
|
type = string
|
||||||
|
default = "https://kubernetes.default.svc.cluster.local:443"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "kubernetes_role_ttl_seconds" {
|
||||||
|
description = "TTL for workload Kubernetes auth tokens."
|
||||||
|
type = number
|
||||||
|
default = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "kubernetes_token_audience" {
|
||||||
|
description = "Audience used by projected service account tokens."
|
||||||
|
type = string
|
||||||
|
default = "vault"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "kv_mount_path" {
|
||||||
|
description = "Workload Vault KV-v2 mount path."
|
||||||
|
type = string
|
||||||
|
default = "kv"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "operator_role_name" {
|
||||||
|
description = "Workload Vault Kubernetes auth role for human operators."
|
||||||
|
type = string
|
||||||
|
default = "vault-operator-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "operator_service_account_name" {
|
||||||
|
description = "Service account authorized to open workload Vault operator sessions."
|
||||||
|
type = string
|
||||||
|
default = "vault-operator"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "operator_service_account_namespace" {
|
||||||
|
description = "Namespace of the workload Vault operator service account."
|
||||||
|
type = string
|
||||||
|
default = "vault"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "operator_token_ttl_seconds" {
|
||||||
|
description = "TTL for workload Vault operator sessions."
|
||||||
|
type = number
|
||||||
|
default = 1800
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "platform_admin_policy_name" {
|
||||||
|
description = "Policy used only for short-lived break-glass administration."
|
||||||
|
type = string
|
||||||
|
default = "platform-admin-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "transit_mount_path" {
|
||||||
|
description = "Workload Vault Transit mount path for application cryptography."
|
||||||
|
type = string
|
||||||
|
default = "transit"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vault_addr" {
|
||||||
|
description = "Workload Vault API address reachable by the approved runner."
|
||||||
|
type = string
|
||||||
|
default = "http://127.0.0.1:8200"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vault_token" {
|
||||||
|
description = "Short-lived token used only for this Terraform run."
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
ephemeral = true
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/vault" {
|
||||||
|
version = "5.7.0"
|
||||||
|
constraints = "~> 5.7.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:Pm0AcUSYmBPZgRahQX/ahiYcjtZODSAEc2rK8r8MQ18=",
|
||||||
|
"zh:1dd9ab6d23f61a5e522efcb462f1fd6f4a210c77b9038c8e12fa5fa663b45d01",
|
||||||
|
"zh:3c98d37ead857c980f7b9285f8c3e1eb7a8fd6d6799275c311c6997973389cc9",
|
||||||
|
"zh:3df895fbaed383e3748ba1b50f5f1046f75503483bc3d783992059f85c85ba31",
|
||||||
|
"zh:3e9faaa0a85c6f03c7fd7f8b7008bb3fbb8777f26c001875947cafa47f91c657",
|
||||||
|
"zh:52a057d0c6cde7cbfd9ceb78a3781dcfc81cf108c533f454530ea6bb87a9bea8",
|
||||||
|
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||||
|
"zh:8521c3825254a5f7fbff8f42ca57cabf052366f0420f5f239ebebf8292c03d0e",
|
||||||
|
"zh:953563d429e40087eb34faf22f28e781e50eee27cfc9ac1ad04308ba592a647f",
|
||||||
|
"zh:a52dd76bb7f5b86cb8de7380d2e68b47ec4445782c16ee205e6a013be35a57b6",
|
||||||
|
"zh:bdad38c95a14c8cce1eeadcc539cf9bf74902ce7c662b79105ad993bb48ec073",
|
||||||
|
"zh:d3c676d7d12c15b58518fa3ee7fc398a13893b4057fe9bf4bc1fe635f3fb995a",
|
||||||
|
"zh:f8673b6c06da80e912c9e32dd4853f07bfca386968d5b33c9fceb6f68b519959",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.11.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "~> 5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backend "s3" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "vault" {
|
||||||
|
address = var.vault_addr
|
||||||
|
skip_child_token = true
|
||||||
|
token = var.vault_token
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
migration_role_name = "auth-db-migration-dev"
|
||||||
|
operator_role_name = "postgres-operator-dev"
|
||||||
|
|
||||||
|
creation_statements = [
|
||||||
|
<<-EOT
|
||||||
|
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
|
||||||
|
GRANT "${var.auth_db_role}" TO "{{name}}";
|
||||||
|
EOT
|
||||||
|
]
|
||||||
|
|
||||||
|
revocation_statements = [
|
||||||
|
<<-EOT
|
||||||
|
REASSIGN OWNED BY "{{name}}" TO "${var.auth_db_role}";
|
||||||
|
DROP OWNED BY "{{name}}";
|
||||||
|
REVOKE "${var.auth_db_role}" FROM "{{name}}";
|
||||||
|
DROP ROLE IF EXISTS "{{name}}";
|
||||||
|
EOT
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_database_secret_backend_connection" "platform_postgres" {
|
||||||
|
allowed_roles = [local.migration_role_name, local.operator_role_name]
|
||||||
|
backend = var.database_mount_path
|
||||||
|
name = var.database_config_name
|
||||||
|
plugin_name = "postgresql-database-plugin"
|
||||||
|
verify_connection = true
|
||||||
|
|
||||||
|
postgresql {
|
||||||
|
connection_url = "postgresql://{{username}}:{{password}}@${var.postgres_host}:${var.postgres_port}/${var.postgres_database}?sslmode=${var.postgres_sslmode}"
|
||||||
|
password_authentication = "scram-sha-256"
|
||||||
|
password_wo = var.postgres_admin_password
|
||||||
|
password_wo_version = var.postgres_admin_password_version
|
||||||
|
username = var.postgres_admin_username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_database_secret_backend_role" "auth_db_migration" {
|
||||||
|
backend = var.database_mount_path
|
||||||
|
creation_statements = local.creation_statements
|
||||||
|
db_name = vault_database_secret_backend_connection.platform_postgres.name
|
||||||
|
default_ttl = var.auth_db_migration_default_ttl_seconds
|
||||||
|
max_ttl = var.auth_db_migration_max_ttl_seconds
|
||||||
|
name = local.migration_role_name
|
||||||
|
revocation_statements = local.revocation_statements
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "vault_database_secret_backend_role" "postgres_operator" {
|
||||||
|
backend = var.database_mount_path
|
||||||
|
creation_statements = local.creation_statements
|
||||||
|
db_name = vault_database_secret_backend_connection.platform_postgres.name
|
||||||
|
default_ttl = var.postgres_operator_default_ttl_seconds
|
||||||
|
max_ttl = var.postgres_operator_max_ttl_seconds
|
||||||
|
name = local.operator_role_name
|
||||||
|
revocation_statements = local.revocation_statements
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
variable "auth_db_migration_default_ttl_seconds" {
|
||||||
|
description = "Default TTL for migration credentials."
|
||||||
|
type = number
|
||||||
|
default = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_db_migration_max_ttl_seconds" {
|
||||||
|
description = "Maximum TTL for migration credentials."
|
||||||
|
type = number
|
||||||
|
default = 86400
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "auth_db_role" {
|
||||||
|
description = "Stable PostgreSQL owner role used by dynamic users."
|
||||||
|
type = string
|
||||||
|
default = "project_auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "database_config_name" {
|
||||||
|
description = "Vault database connection name."
|
||||||
|
type = string
|
||||||
|
default = "platform-postgres-dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "database_mount_path" {
|
||||||
|
description = "Foundation-owned database secrets mount path."
|
||||||
|
type = string
|
||||||
|
default = "database"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_admin_password" {
|
||||||
|
description = "PostgreSQL admin password passed only through a write-only provider field."
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
ephemeral = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_admin_password_version" {
|
||||||
|
description = "Increment whenever postgres_admin_password is rotated."
|
||||||
|
type = number
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_admin_username" {
|
||||||
|
description = "Dedicated database administration username."
|
||||||
|
type = string
|
||||||
|
default = "postgres"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_database" {
|
||||||
|
description = "Database in which dynamic migration objects are owned and revoked."
|
||||||
|
type = string
|
||||||
|
default = "project_auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_host" {
|
||||||
|
description = "Platform PostgreSQL service DNS name."
|
||||||
|
type = string
|
||||||
|
default = "postgres.platform.svc.cluster.local"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_operator_default_ttl_seconds" {
|
||||||
|
description = "Default TTL for operator database credentials."
|
||||||
|
type = number
|
||||||
|
default = 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_operator_max_ttl_seconds" {
|
||||||
|
description = "Maximum TTL for operator database credentials."
|
||||||
|
type = number
|
||||||
|
default = 28800
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_port" {
|
||||||
|
description = "Platform PostgreSQL service port."
|
||||||
|
type = number
|
||||||
|
default = 5432
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "postgres_sslmode" {
|
||||||
|
description = "PostgreSQL SSL mode. Dev currently uses disable; production must use verify-full."
|
||||||
|
type = string
|
||||||
|
default = "disable"
|
||||||
|
|
||||||
|
validation {
|
||||||
|
condition = contains(["disable", "require", "verify-ca", "verify-full"], var.postgres_sslmode)
|
||||||
|
error_message = "postgres_sslmode must be disable, require, verify-ca, or verify-full."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vault_addr" {
|
||||||
|
description = "Workload Vault API address."
|
||||||
|
type = string
|
||||||
|
default = "http://127.0.0.1:8200"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vault_token" {
|
||||||
|
description = "Short-lived token carrying vault-database-automation-dev."
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
ephemeral = true
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
resource "vault_kubernetes_auth_backend_role" "this" {
|
||||||
|
for_each = var.roles
|
||||||
|
|
||||||
|
audience = one(each.value.audiences)
|
||||||
|
backend = var.backend
|
||||||
|
bound_service_account_names = each.value.service_account_names
|
||||||
|
bound_service_account_namespaces = each.value.service_account_namespaces
|
||||||
|
role_name = each.key
|
||||||
|
token_policies = each.value.token_policies
|
||||||
|
token_ttl = each.value.token_ttl
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
variable "backend" {
|
||||||
|
description = "Kubernetes auth backend path."
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "roles" {
|
||||||
|
description = "Kubernetes auth roles keyed by Vault role name."
|
||||||
|
type = map(object({
|
||||||
|
audiences = set(string)
|
||||||
|
service_account_names = set(string)
|
||||||
|
service_account_namespaces = set(string)
|
||||||
|
token_policies = set(string)
|
||||||
|
token_ttl = number
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.11.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "~> 5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
resource "vault_policy" "this" {
|
||||||
|
for_each = var.policies
|
||||||
|
|
||||||
|
name = each.key
|
||||||
|
policy = each.value
|
||||||
|
}
|
||||||
|
|
||||||
|
output "names" {
|
||||||
|
description = "Policy names keyed by their requested names."
|
||||||
|
value = { for name, policy in vault_policy.this : name => policy.name }
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
variable "policies" {
|
||||||
|
description = "Map of Vault policy names to HCL policy documents."
|
||||||
|
type = map(string)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.11.0"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
vault = {
|
||||||
|
source = "hashicorp/vault"
|
||||||
|
version = "~> 5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user