Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions .github/workflows/platform-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,29 +78,44 @@ jobs:
tar xf kubeconform-linux-amd64.tar.gz
sudo mv kubeconform /usr/local/bin/

- name: Install helm unittest plugin
run: |
rm -rf "${HOME}/.local/share/helm/plugins/helm-unittest.git" 2>/dev/null || true
helm plugin install https://github.com/helm-unittest/helm-unittest.git --version 0.7.0

- name: Helm lint
run: |
helm lint standardized-path/app -f platform/apps/dev/values.yaml
helm lint standardized-path/app -f platform/apps/stage/values.yaml
helm lint standardized-path/app -f platform/apps/prod/values.yaml
helm lint standardized-path/app -f platform/apps/app-b/dev/values.yaml
helm lint standardized-path/app -f platform/apps/app-b/stage/values.yaml
helm lint standardized-path/app -f platform/apps/app-b/prod/values.yaml

- name: Helm unit tests
run: helm unittest standardized-path/app
Comment on lines +95 to +96

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for missing Helm unittest plugin. The workflow will fail if the plugin isn't installed.

Suggested change
- name: Helm unit tests
run: helm unittest standardized-path/app
- name: Setup Helm unittest plugin
run: helm plugin install || true
- name: Helm unit tests
run: helm unittest standardized-path/app


- name: Render manifests
run: |
helm template simple-app-dev standardized-path/app -f platform/apps/dev/values.yaml > /tmp/dev.yaml
helm template simple-app-stage standardized-path/app -f platform/apps/stage/values.yaml > /tmp/stage.yaml
helm template simple-app-prod standardized-path/app -f platform/apps/prod/values.yaml > /tmp/prod.yaml
helm template simple-app-dev standardized-path/app -f platform/apps/dev/values.yaml > /tmp/simple-app-dev.yaml
helm template simple-app-stage standardized-path/app -f platform/apps/stage/values.yaml > /tmp/simple-app-stage.yaml
helm template simple-app-prod standardized-path/app -f platform/apps/prod/values.yaml > /tmp/simple-app-prod.yaml
helm template app-b-dev standardized-path/app -f platform/apps/app-b/dev/values.yaml > /tmp/app-b-dev.yaml
helm template app-b-stage standardized-path/app -f platform/apps/app-b/stage/values.yaml > /tmp/app-b-stage.yaml
helm template app-b-prod standardized-path/app -f platform/apps/app-b/prod/values.yaml > /tmp/app-b-prod.yaml

- name: Validate manifests with kubeconform
run: |
kubeconform -summary -output json /tmp/dev.yaml
kubeconform -summary -output json /tmp/stage.yaml
kubeconform -summary -output json /tmp/prod.yaml
for f in /tmp/simple-app-*.yaml /tmp/app-b-*.yaml; do
kubeconform -summary -ignore-missing-schemas -output json "$f"
done

- name: Validate Argo CD applications
run: |
kubeconform -summary -skip Application platform/apps/dev/simple-app.yaml
kubeconform -summary -skip Application platform/apps/stage/simple-app.yaml
kubeconform -summary -skip Application platform/apps/prod/simple-app.yaml
for f in platform/apps/dev/*.yaml platform/apps/stage/*.yaml platform/apps/prod/*.yaml; do
[[ "$(basename "$f")" == "values.yaml" ]] && continue
kubeconform -summary -skip Application -ignore-missing-schemas "$f"
done

- name: Validate bootstrap manifests
run: kubeconform -summary -ignore-missing-schemas platform/bootstrap/*.yaml
Expand All @@ -116,6 +131,9 @@ jobs:
helm template simple-app-dev standardized-path/app -f platform/apps/dev/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -
helm template simple-app-stage standardized-path/app -f platform/apps/stage/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -
helm template simple-app-prod standardized-path/app -f platform/apps/prod/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -
helm template app-b-dev standardized-path/app -f platform/apps/app-b/dev/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -
helm template app-b-stage standardized-path/app -f platform/apps/app-b/stage/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -
helm template app-b-prod standardized-path/app -f platform/apps/app-b/prod/values.yaml | tr -d '\r' | conftest test -p policy/eks/ --no-color -

openshift-validate:
runs-on: ubuntu-latest
Expand Down
127 changes: 127 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.ONESHELL:
.DELETE_ON_ERROR:
MAKEFLAGS += --no-print-directory

CLUSTER_NAME ?= platform-demo
NAMESPACE ?= argocd
KIND_IMG ?= kindest/node:v1.30.0

.PHONY: help
help:
@echo 'Usage: make <target>'
@echo ''
@echo ' setup Create cluster + install Argo CD + deploy platform + deploy demo app'
@echo ' kind-up Create Kind cluster'
@echo ' kind-down Delete Kind cluster'
@echo ' argocd-install Install Argo CD and apply AppProjects'
@echo ' deploy Apply platform bootstrap + add-ons + demo app'
@echo ' test Run Helm unit tests'
@echo ' validate Run CI checks locally (Helm lint + test + conftest)'
@echo ' clean kind-down + remove tmp files'
@echo ''

.PHONY: kind-up
kind-up:
@echo "=== Creating Kind cluster: $(CLUSTER_NAME) ==="
kind create cluster --name $(CLUSTER_NAME) --config kind-config.yaml
@echo "Cluster ready: $(CLUSTER_NAME)"

.PHONY: kind-down
kind-down:
@echo "=== Deleting Kind cluster: $(CLUSTER_NAME) ==="
kind delete cluster --name $(CLUSTER_NAME) 2>/dev/null || true

.PHONY: argocd-install
argocd-install: kind-up
@echo "=== Installing Argo CD ==="
cd argocd/bootstrap && ./install.sh

.PHONY: deploy
deploy:
@echo "=== Applying platform bootstrap ==="
kubectl apply -f platform/bootstrap/namespaces.yaml
kubectl apply -f platform/bootstrap/resource-quota.yaml
kubectl apply -f platform/bootstrap/limit-range.yaml
kubectl apply -f platform/bootstrap/network-policy.yaml
kubectl apply -f platform/bootstrap/rbac-readonly.yaml
kubectl apply -f platform/bootstrap/cluster-secret-store.yaml

@echo "=== Creating platform secrets namespace ==="
kubectl create namespace platform-secrets --dry-run=client -o yaml | kubectl apply -f -

@echo "=== Seeding dev secret for demo ==="
kubectl create secret generic dev-simple-app-db-password \
--from-literal=db-password=$${DEV_DB_PASSWORD:-changeme} \
-n platform-secrets --dry-run=client -o yaml | kubectl apply -f -
Comment on lines +55 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Hardcoded secret value exposes credentials in source control. Replace with environment variable or secure secret management.1

Suggested change
kubectl create secret generic dev-simple-app-db-password \
--from-literal=db-password=local-dev-password \
-n platform-secrets --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic dev-simple-app-db-password \
--from-literal=db-password=$${DEV_DB_PASSWORD:-changeme} \
-n platform-secrets --dry-run=client -o yaml | kubectl apply -f -

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html


@echo "=== Applying platform add-ons ==="
kubectl apply -f platform/addons/metrics-server.yaml
kubectl apply -f platform/addons/nginx-ingress.yaml
kubectl apply -f platform/addons/external-secrets.yaml
kubectl apply -f platform/addons/kube-prometheus-stack.yaml
kubectl apply -f platform/addons/grafana-dashboards-platform.yaml

@echo "=== Deploying simple-app ==="
helm template simple-app-dev standardized-path/app \
-f platform/apps/dev/values.yaml \
| kubectl apply -f - 2>&1 | grep -v 'unchanged' || true

@echo "=== Deploying app-b ==="
helm template app-b-dev standardized-path/app \
-f platform/apps/app-b/dev/values.yaml \
| kubectl apply -f - 2>&1 | grep -v 'unchanged' || true

@echo "=== App status ==="
kubectl get pods -n dev --show-labels

.PHONY: test
test:
@echo "=== Helm unit tests ==="
helm unittest standardized-path/app --color

.PHONY: validate
validate: test
@echo "=== Helm lint ==="
helm lint standardized-path/app -f platform/apps/dev/values.yaml
helm lint standardized-path/app -f platform/apps/stage/values.yaml
helm lint standardized-path/app -f platform/apps/prod/values.yaml

@echo "=== ServiceMonitor renders ==="
helm template simple-app-dev standardized-path/app -f platform/apps/dev/values.yaml \
| grep -q "kind: ServiceMonitor" && echo " PASS: ServiceMonitor present"

@echo "=== OpenShift path renders correctly ==="
helm template test standardized-path/app \
--set openshift.enabled=true \
--set openshift.route.enabled=true \
--set ingress.enabled=false \
| grep -q "kind: Route" && echo " PASS: Route present"

@echo "=== OPA policy: EKS manifests ==="
helm template simple-app-dev standardized-path/app -f platform/apps/dev/values.yaml \
| tr -d '\r' | conftest test -p policy/eks/ --no-color -

@echo "=== OPA policy: OpenShift manifests ==="
helm template test standardized-path/app \
--set openshift.enabled=true \
--set openshift.route.enabled=true \
--set ingress.enabled=false \
| tr -d '\r' | conftest test -p policy/openshift/ --no-color -

.PHONY: setup
setup: argocd-install deploy
@echo "=== Setup complete ==="
@echo ""
@echo " Argo CD UI: http://localhost:8080 (admin / get password below)"
@echo " Demo app: kubectl get all -n dev"
@echo ""
@echo " Argo CD password:"
@kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" 2>/dev/null | base64 -d || echo " (retrievable after install completes)"

.PHONY: clean
clean: kind-down
@rm -f /tmp/platform-*.yaml 2>/dev/null || true
@echo "Done."
66 changes: 61 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,42 @@ A portable internal developer platform built for Amazon EKS and Red Hat OpenShif
- **Cluster-portable contract** — same Helm chart, same GitOps model, same CI pipeline works on EKS and OpenShift; toggle with one values flag
- **Admission-level security** — OpenShift SCC enforced at cluster level vs. overridable `securityContext` on EKS, with automatic handling in templates
- **GitOps discipline** — Argo CD app-of-apps pattern, drift detection, environment promotion via Git, no `kubectl apply` for steady state
- **2 tenant apps** (simple-app + app-b) — same Helm contract, different values files; proves the contract scales to multiple services
- **3 environments** — dev → stage → prod promotion through reviewed PRs with increasing replica counts and resource quotas
- **9 CI checks** — Terraform fmt/validate/lint, Trivy scan, Helm lint, manifest rendering, kubeconform, OPA policy validation, OpenShift structural validation
- **Built-in observability** — Prometheus + Grafana with pre-built dashboards, auto-discovered via ServiceMonitor
- **Secret management** — External Secrets Operator with ClusterSecretStore and Helm-integrated ExternalSecret template
- **10 CI checks** — Terraform fmt/validate/lint, Trivy scan, Helm lint, Helm unit tests (28), manifest rendering, kubeconform, OPA policy validation, OpenShift structural validation

---

## Quickstart

```bash
# Prerequisites: kind, docker, kubectl, helm, conftest (see below)

# One command — creates cluster, installs Argo CD, deploys platform + demo app
make setup
```

Or step by step:

```bash
make kind-up # Create local Kind cluster
make validate # Run CI checks locally (no cluster needed)
make argocd-install # Install Argo CD + AppProjects
make deploy # Bootstrap + add-ons + demo app
make clean # Delete cluster
```

**Prerequisites:**

| Tool | Install |
|---|---|
| Docker | [docs.docker.com/engine/install](https://docs.docker.com/engine/install/) |
| kind | `go install sigs.k8s.io/kind@latest` |
| kubectl | `curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"` |
| Helm | `curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \| bash` |
| conftest | `wget "https://github.com/open-policy-agent/conftest/releases/latest/download/conftest_$(uname -s)_$(uname -m).tar.gz" && tar xzf conftest_*.tar.gz && sudo mv conftest /usr/local/bin/` |

---

Expand Down Expand Up @@ -76,6 +110,21 @@ The chart detects the target automatically: when `openshift.enabled=true`, pod-l

---

## Observability

Prometheus and Grafana are deployed as Argo CD-managed add-ons with a pre-built **Platform App Overview** dashboard covering CPU, memory, request rate, and pod status across all environments.

| Component | Data source | How it's discovered |
|---|---|---|
| Prometheus | kube-prometheus-stack | ServiceMonitor with label `app.kubernetes.io/managed-by: platform` |
| Grafana | Prometheus | Dashboard ConfigMap auto-loaded via provider config |
| Metrics | kube-state-metrics + cAdvisor | Default kube-prometheus-stack scrape configs |
| App metrics | `/metrics` endpoint | ServiceMonitor template in the Helm chart (enabled by default) |

Every app deployed through the platform contract automatically gets a ServiceMonitor — teams don't need to configure scraping.

---

## Key decisions

| Decision | Why |
Expand All @@ -91,18 +140,18 @@ The chart detects the target automatically: when `openshift.enabled=true`, pod-l
## What I'd do next

- Add OPA/Gatekeeper policies for platform-level validation beyond what SCC provides
- Integrate external-secrets operator for cloud-provider-backed secret management
- Add observability defaults (Prometheus + Grafana dashboards per namespace)
- Replace `legacy/` assets with a clean `examples/` directory

---

## Repo layout

```
Makefile # One-command setup: kind cluster, Argo CD, deploy, validate, clean
kind-config.yaml # Kind cluster configuration with port mappings
argocd/ # AppProjects, app-of-apps, ApplicationSets, config, bootstrap
infra/ # VPC, IAM, EKS (Terraform modules)
platform/ # Namespaces, quotas, network policies, add-ons, env overrides
platform/ # Namespaces, quotas, network policies, add-ons, Grafana dashboards, env overrides
standardized-path/ # Golden path Helm chart (the tenant contract)
policy/ # OPA/Rego policies for EKS and OpenShift security validation
docs/ # Architecture, operations, tenant contract
Expand All @@ -112,9 +161,16 @@ docs/ # Architecture, operations, tenant contract

## Running CI

All validation runs on push/PR. Key checks:
All validation runs on push/PR. Run locally:

```bash
make validate
```

Individual checks:

```bash
make test # 28 unit tests across 7 suites
helm lint standardized-path/app -f platform/apps/dev/values.yaml
helm template test standardized-path/app | kubeconform -summary
helm template test standardized-path/app \
Expand Down
30 changes: 18 additions & 12 deletions argocd/appsets/environments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@ spec:
- list:
elements:
- env: dev
replicas: 1
imageTag: 1.0.0-dev.1
message: "Hello from the dev platform"
app: simple-app
valuesPath: platform/apps/dev/values.yaml
- env: stage
replicas: 2
imageTag: 1.0.0-rc1
message: "Hello from the stage platform"
app: simple-app
valuesPath: platform/apps/stage/values.yaml
- env: prod
replicas: 3
imageTag: 1.0.0
message: "Hello from the prod platform"
app: simple-app
valuesPath: platform/apps/prod/values.yaml
- env: dev
app: app-b
valuesPath: platform/apps/app-b/dev/values.yaml
- env: stage
app: app-b
valuesPath: platform/apps/app-b/stage/values.yaml
- env: prod
app: app-b
valuesPath: platform/apps/app-b/prod/values.yaml
template:
metadata:
name: "simple-app-{{env}}"
name: "{{app}}-{{env}}"
labels:
app: simple-app
app: "{{app}}"
environment: "{{env}}"
spec:
project: tenant-apps
Expand All @@ -33,7 +39,7 @@ spec:
path: standardized-path/app
helm:
valueFiles:
- "/platform/apps/{{env}}/values.yaml"
- "/{{valuesPath}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{env}}"
Expand Down
Loading
Loading