diff --git a/apps/base/artifact-keeper/cnpg_cluster.yaml b/apps/base/artifact-keeper/cnpg_cluster.yaml new file mode 100644 index 0000000..1e92a76 --- /dev/null +++ b/apps/base/artifact-keeper/cnpg_cluster.yaml @@ -0,0 +1,94 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: artifact-keeper-postgres + namespace: artifact-keeper +spec: + affinity: + podAntiAffinityType: preferred + bootstrap: + initdb: + database: artifact_registry + encoding: UTF8 + localeCType: C + localeCollate: C + owner: registry + secret: + name: postgres-credentials + postInitSQL: + - CREATE DATABASE dependency_track OWNER registry; + - GRANT ALL PRIVILEGES ON DATABASE dependency_track TO registry; + enablePDB: true + enableSuperuserAccess: false + failoverDelay: 0 + imageName: ghcr.io/cloudnative-pg/postgresql:17-minimal-trixie + instances: 3 + logLevel: info + maxSyncReplicas: 0 + minSyncReplicas: 0 + monitoring: + customQueriesConfigMap: + - key: queries + name: cnpg-default-monitoring + disableDefaultQueries: false + enablePodMonitor: false + postgresql: + parameters: + archive_mode: "on" + archive_timeout: 5min + dynamic_shared_memory_type: posix + effective_cache_size: 256MB + full_page_writes: "on" + log_destination: csvlog + log_directory: /controller/log + log_filename: postgres + log_rotation_age: "0" + log_rotation_size: "0" + log_truncate_on_rotation: "false" + logging_collector: "on" + max_connections: "200" + max_parallel_workers: "16" + max_replication_slots: "16" + max_worker_processes: "16" + shared_buffers: 128MB + shared_memory_type: mmap + ssl_max_protocol_version: TLSv1.3 + ssl_min_protocol_version: TLSv1.3 + wal_keep_size: 256MB + wal_level: logical + wal_log_hints: "on" + wal_receiver_timeout: 5s + wal_sender_timeout: 5s + syncReplicaElectionConstraint: + enabled: false + primaryUpdateMethod: restart + primaryUpdateStrategy: unsupervised + probes: + liveness: + isolationCheck: + connectionTimeout: 1000 + enabled: true + requestTimeout: 1000 + replicationSlots: + highAvailability: + enabled: true + slotPrefix: _cnpg_ + synchronizeReplicas: + enabled: true + updateInterval: 30 + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 250m + memory: 256Mi + smartShutdownTimeout: 180 + startDelay: 3600 + stopDelay: 1800 + storage: + resizeInUseVolumes: true + size: 20Gi + storageClass: cephrbd-fast-delete + switchoverDelay: 3600 diff --git a/apps/base/artifact-keeper/cnpg_pooler.yaml b/apps/base/artifact-keeper/cnpg_pooler.yaml new file mode 100644 index 0000000..44fcb32 --- /dev/null +++ b/apps/base/artifact-keeper/cnpg_pooler.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Pooler +metadata: + name: artifact-keeper-postgres-pooler + namespace: artifact-keeper +spec: + cluster: + name: artifact-keeper-postgres + instances: 2 + pgbouncer: + parameters: + default_pool_size: "100" + max_client_conn: "400" + paused: false + poolMode: session + template: + metadata: + labels: + app: artifact-keeper-pooler + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifact-keeper-pooler + topologyKey: kubernetes.io/hostname + containers: [] + type: rw diff --git a/apps/base/artifact-keeper/configmap_app-config.yaml b/apps/base/artifact-keeper/configmap_app-config.yaml new file mode 100644 index 0000000..39ed504 --- /dev/null +++ b/apps/base/artifact-keeper/configmap_app-config.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper +data: + BIND_ADDRESS: "0.0.0.0:8080" + LOG_LEVEL: "info,artifact_keeper=debug" + STORAGE_BACKEND: "s3" + MEILISEARCH_URL: "http://meilisearch:7700" + TRIVY_URL: "http://trivy:8090" + DEPENDENCY_TRACK_URL: "http://dtrack:8080" + DEPENDENCY_TRACK_ENABLED: "true" + SCAN_WORKSPACE_PATH: "/scan-workspace" + PLUGINS_DIR: "/data/plugins" diff --git a/apps/base/artifact-keeper/configmap_s3-env.yaml b/apps/base/artifact-keeper/configmap_s3-env.yaml new file mode 100644 index 0000000..3e133d0 --- /dev/null +++ b/apps/base/artifact-keeper/configmap_s3-env.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: s3-env + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper +data: + S3_ENDPOINT: "https://radosgw.service.consul" + S3_BUCKET: "artifact-keeper" + S3_REGION: "ap-southeast-2" + S3_PATH_STYLE: "true" diff --git a/apps/base/artifact-keeper/deployment_backend.yaml b/apps/base/artifact-keeper/deployment_backend.yaml new file mode 100644 index 0000000..55e07b0 --- /dev/null +++ b/apps/base/artifact-keeper/deployment_backend.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: backend + annotations: + reloader.stakater.com/auto: "true" +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: backend + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: backend + spec: + serviceAccountName: backend + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - backend + topologyKey: kubernetes.io/hostname + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi + command: ["/bin/sh", "-c"] + args: + - | + echo "Waiting for PostgreSQL..." + until pg_isready -h artifact-keeper-postgres-pooler -p 5432 -U registry; do + sleep 3 + done + echo "PostgreSQL is ready" + - name: wait-for-meilisearch + image: alpine:3.20 + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 16Mi + ephemeral-storage: 32Mi + limits: + cpu: 100m + memory: 64Mi + ephemeral-storage: 64Mi + command: ["/bin/sh", "-c"] + args: + - | + echo "Waiting for Meilisearch..." + until wget -qO- http://meilisearch:7700/health >/dev/null 2>&1; do + sleep 3 + done + echo "Meilisearch is ready" + containers: + - name: backend + image: "ghcr.io/artifact-keeper/artifact-keeper-backend:dev" + imagePullPolicy: Always + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + command: ["/bin/sh", "-c"] + args: + - | + if [ -f /shared/dtrack-api-key ] && [ -s /shared/dtrack-api-key ]; then + export DEPENDENCY_TRACK_API_KEY="$(cat /shared/dtrack-api-key)" + fi + exec /usr/local/bin/artifact-keeper + ports: + - name: http + containerPort: 8080 + protocol: TCP + - name: grpc + containerPort: 9090 + protocol: TCP + envFrom: + - configMapRef: + name: config + - configMapRef: + name: s3-env + - secretRef: + name: s3-credentials + - secretRef: + name: app-secrets + resources: + limits: + cpu: "2" + memory: 2Gi + requests: + cpu: 250m + memory: 256Mi + livenessProbe: + httpGet: + path: /livez + port: http + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 5 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: storage + mountPath: /data/storage + subPath: storage + - name: storage + mountPath: /data/backups + subPath: backups + - name: storage + mountPath: /data/plugins + subPath: plugins + - name: scan-workspace + mountPath: /scan-workspace + - name: shared-config + mountPath: /shared + readOnly: true + volumes: + - name: tmp + emptyDir: + sizeLimit: 256Mi + - name: storage + persistentVolumeClaim: + claimName: storage + - name: scan-workspace + persistentVolumeClaim: + claimName: scan-workspace + - name: shared-config + persistentVolumeClaim: + claimName: shared-config diff --git a/apps/base/artifact-keeper/deployment_dtrack.yaml b/apps/base/artifact-keeper/deployment_dtrack.yaml new file mode 100644 index 0000000..aaecbc6 --- /dev/null +++ b/apps/base/artifact-keeper/deployment_dtrack.yaml @@ -0,0 +1,111 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dtrack + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: dependency-track +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: dependency-track + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: dependency-track + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi + command: ["/bin/sh", "-c"] + args: + - | + echo "Waiting for PostgreSQL..." + until pg_isready -h artifact-keeper-postgres-pooler -p 5432 -U registry; do + sleep 3 + done + echo "PostgreSQL is ready" + containers: + - name: dtrack-api + image: "dependencytrack/apiserver:4.11.4" + imagePullPolicy: IfNotPresent + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + - name: ALPINE_DATABASE_MODE + value: "external" + - name: ALPINE_DATABASE_URL + value: "jdbc:postgresql://artifact-keeper-postgres-pooler:5432/dependency_track" + - name: ALPINE_DATABASE_DRIVER + value: "org.postgresql.Driver" + - name: ALPINE_DATABASE_USERNAME + value: "registry" + - name: ALPINE_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-credentials + key: password + - name: ALPINE_DATA_DIRECTORY + value: "/data" + - name: ALPINE_ENFORCE_AUTHENTICATION + value: "true" + - name: ALPINE_CORS_ENABLED + value: "true" + - name: ALPINE_CORS_ALLOW_ORIGIN + value: "*" + - name: JAVA_OPTIONS + value: "-Xmx4g" + resources: + limits: + cpu: "2" + memory: 6Gi + requests: + cpu: 250m + memory: 4Gi + volumeMounts: + - name: dtrack-data + mountPath: /data + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: + sizeLimit: 256Mi + - name: dtrack-data + persistentVolumeClaim: + claimName: dtrack diff --git a/apps/base/artifact-keeper/deployment_meilisearch.yaml b/apps/base/artifact-keeper/deployment_meilisearch.yaml new file mode 100644 index 0000000..71e44fd --- /dev/null +++ b/apps/base/artifact-keeper/deployment_meilisearch.yaml @@ -0,0 +1,154 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: meilisearch + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: meilisearch +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: meilisearch + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: meilisearch + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + initContainers: + - name: fix-ownership + image: busybox:1.37 + securityContext: + runAsNonRoot: false + runAsUser: 0 + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - CHOWN + - FOWNER + resources: + requests: + cpu: 10m + memory: 16Mi + ephemeral-storage: 32Mi + limits: + cpu: 100m + memory: 64Mi + ephemeral-storage: 64Mi + command: ["sh", "-c", "chown -R 1000:1000 /meili_data"] + volumeMounts: + - name: meilisearch-data + mountPath: /meili_data + - name: version-guard + image: busybox:1.37 + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi + command: ["sh", "-c"] + args: + - | + EXPECTED="v1.12" + VERSION_FILE="/meili_data/data.ms/VERSION" + if [ ! -f "$VERSION_FILE" ]; then + echo "No existing database, fresh start" + exit 0 + fi + CURRENT=$(cat "$VERSION_FILE" 2>/dev/null || echo "unknown") + echo "Current DB version: $CURRENT, expected image: $EXPECTED" + if echo "$CURRENT" | grep -qv "$(echo $EXPECTED | sed 's/^v//')"; then + echo "Version mismatch — wiping data.ms for clean re-index" + rm -rf /meili_data/data.ms + echo "Done. Backend will re-index automatically." + else + echo "Versions match, keeping existing data" + fi + volumeMounts: + - name: meilisearch-data + mountPath: /meili_data + containers: + - name: meilisearch + image: "getmeili/meilisearch:v1.12" + imagePullPolicy: IfNotPresent + securityContext: + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + ports: + - name: http + containerPort: 7700 + protocol: TCP + env: + - name: MEILI_MASTER_KEY + valueFrom: + secretKeyRef: + name: app-secrets + key: MEILISEARCH_API_KEY + - name: MEILI_ENV + value: "production" + - name: MEILI_MAX_INDEXING_THREADS + value: "4" + resources: + limits: + cpu: "1" + memory: 8Gi + requests: + cpu: 250m + memory: 512Mi + readinessProbe: + httpGet: + path: /health + port: 7700 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + livenessProbe: + httpGet: + path: /health + port: 7700 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + volumeMounts: + - name: meilisearch-data + mountPath: /meili_data + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: + sizeLimit: 256Mi + - name: meilisearch-data + persistentVolumeClaim: + claimName: meilisearch diff --git a/apps/base/artifact-keeper/deployment_trivy.yaml b/apps/base/artifact-keeper/deployment_trivy.yaml new file mode 100644 index 0000000..46e440a --- /dev/null +++ b/apps/base/artifact-keeper/deployment_trivy.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: trivy + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: trivy +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: trivy + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: trivy + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 10000 + fsGroup: 10000 + containers: + - name: trivy + image: "aquasec/trivy:0.62.1" + imagePullPolicy: IfNotPresent + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + command: ["trivy"] + args: ["server", "--listen", "0.0.0.0:8090", "--cache-dir", "/home/trivy/.cache"] + ports: + - name: http + containerPort: 8090 + protocol: TCP + resources: + limits: + cpu: "1" + memory: 2Gi + requests: + cpu: 250m + memory: 256Mi + readinessProbe: + tcpSocket: + port: 8090 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + livenessProbe: + tcpSocket: + port: 8090 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + volumeMounts: + - name: trivy-cache + mountPath: /home/trivy/.cache + - name: tmp + mountPath: /tmp + - name: scan-workspace + mountPath: /scan-workspace + readOnly: true + volumes: + - name: tmp + emptyDir: + sizeLimit: 256Mi + - name: trivy-cache + persistentVolumeClaim: + claimName: trivy-cache + - name: scan-workspace + persistentVolumeClaim: + claimName: scan-workspace diff --git a/apps/base/artifact-keeper/deployment_web.yaml b/apps/base/artifact-keeper/deployment_web.yaml new file mode 100644 index 0000000..26e1bd3 --- /dev/null +++ b/apps/base/artifact-keeper/deployment_web.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: web + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: web +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: web + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: web + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - web + topologyKey: kubernetes.io/hostname + containers: + - name: web + image: "ghcr.io/artifact-keeper/artifact-keeper-web:dev" + imagePullPolicy: Always + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + ports: + - name: http + containerPort: 3000 + protocol: TCP + env: + - name: NEXT_PUBLIC_API_URL + value: "" + - name: BACKEND_URL + value: "http://backend:8080" + - name: NODE_ENV + value: "production" + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 250m + memory: 256Mi + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 5 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 20 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 5 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nextjs-cache + mountPath: /app/.next/cache + volumes: + - name: tmp + emptyDir: + sizeLimit: 256Mi + - name: nextjs-cache + emptyDir: + sizeLimit: 1Gi diff --git a/apps/base/artifact-keeper/ingress.yaml b/apps/base/artifact-keeper/ingress.yaml new file mode 100644 index 0000000..b880b1c --- /dev/null +++ b/apps/base/artifact-keeper/ingress.yaml @@ -0,0 +1,286 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: artifact-keeper + namespace: artifact-keeper + annotations: + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 10g + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + cert-manager.io/cluster-issuer: vault-issuer + cert-manager.io/common-name: artifacts.k8s.syd1.au.unkin.net + cert-manager.io/private-key-size: "4096" + external-dns.alpha.kubernetes.io/hostname: artifacts.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.0 + nginx.ingress.kubernetes.io/ssl-redirect: "false" +spec: + ingressClassName: nginx + rules: + - host: artifacts.k8s.syd1.au.unkin.net + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /health + pathType: Exact + backend: + service: + name: backend + port: + number: 8080 + - path: /ready + pathType: Exact + backend: + service: + name: backend + port: + number: 8080 + - path: /v2 + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /maven + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /npm + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /pypi + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /nuget + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /cargo + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /gems + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /go + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /helm + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /debian + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /rpm + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /alpine + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /composer + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /conan + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /conda + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /swift + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /terraform + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /cocoapods + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /hex + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /pub + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /lfs + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /ivy + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /chef + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /puppet + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /ansible + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /cran + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /huggingface + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /jetbrains + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /vscode + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /proto + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /incus + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /ext + pathType: Prefix + backend: + service: + name: backend + port: + number: 8080 + - path: /dtrack + pathType: Prefix + backend: + service: + name: dtrack + port: + number: 8080 + - path: / + pathType: Prefix + backend: + service: + name: web + port: + number: 3000 + tls: + - hosts: + - artifacts.k8s.syd1.au.unkin.net + secretName: artifacts-tls diff --git a/apps/base/artifact-keeper/job_dtrack-init.yaml b/apps/base/artifact-keeper/job_dtrack-init.yaml new file mode 100644 index 0000000..eca8274 --- /dev/null +++ b/apps/base/artifact-keeper/job_dtrack-init.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: dtrack-init + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: dependency-track +spec: + backoffLimit: 3 + template: + metadata: + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: dependency-track + spec: + restartPolicy: OnFailure + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: dtrack-init + image: alpine:3.20 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 32Mi + ephemeral-storage: 64Mi + limits: + cpu: 200m + memory: 128Mi + ephemeral-storage: 128Mi + command: ["/bin/sh", "-c"] + args: + - | + apk add --no-cache curl jq >/dev/null 2>&1 + /bin/sh /scripts/init-dtrack.sh + env: + - name: DEPENDENCY_TRACK_URL + value: "http://dtrack:8080" + - name: DEPENDENCY_TRACK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: app-secrets + key: DEPENDENCY_TRACK_ADMIN_PASSWORD + volumeMounts: + - name: init-script + mountPath: /scripts + readOnly: true + - name: shared-config + mountPath: /shared + volumes: + - name: init-script + configMap: + name: dtrack-init + defaultMode: 0755 + - name: shared-config + persistentVolumeClaim: + claimName: shared-config diff --git a/apps/base/artifact-keeper/kustomization.yaml b/apps/base/artifact-keeper/kustomization.yaml new file mode 100644 index 0000000..eca3c80 --- /dev/null +++ b/apps/base/artifact-keeper/kustomization.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - serviceaccount_backend.yaml + - cnpg_cluster.yaml + - cnpg_pooler.yaml + - vaultauth.yaml + - vaultstaticsecret.yaml + - configmap_app-config.yaml + - configmap_s3-env.yaml + - persistentvolumeclaims.yaml + - service_backend.yaml + - service_dtrack.yaml + - service_meilisearch.yaml + - service_trivy.yaml + - service_web.yaml + - deployment_backend.yaml + - deployment_dtrack.yaml + - deployment_meilisearch.yaml + - deployment_trivy.yaml + - deployment_web.yaml + - job_dtrack-init.yaml + - ingress.yaml + +configMapGenerator: + - name: dtrack-init + files: + - resources/init-dtrack.sh + options: + disableNameSuffixHash: true diff --git a/apps/base/artifact-keeper/namespace.yaml b/apps/base/artifact-keeper/namespace.yaml new file mode 100644 index 0000000..77da9fb --- /dev/null +++ b/apps/base/artifact-keeper/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper diff --git a/apps/base/artifact-keeper/persistentvolumeclaims.yaml b/apps/base/artifact-keeper/persistentvolumeclaims.yaml new file mode 100644 index 0000000..540b580 --- /dev/null +++ b/apps/base/artifact-keeper/persistentvolumeclaims.yaml @@ -0,0 +1,78 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: storage + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteMany + storageClassName: cephfs-raid5-delete + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: scan-workspace + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteMany + storageClassName: cephfs-raid5-delete + resources: + requests: + storage: 2Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: shared-config + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteMany + storageClassName: cephfs-raid5-delete + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dtrack + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteOnce + storageClassName: cephrbd-fast-delete + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: meilisearch + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteOnce + storageClassName: cephrbd-fast-delete + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: trivy-cache + namespace: artifact-keeper +spec: + accessModes: + - ReadWriteOnce + storageClassName: cephrbd-fast-delete + resources: + requests: + storage: 5Gi diff --git a/apps/base/artifact-keeper/resources/init-dtrack.sh b/apps/base/artifact-keeper/resources/init-dtrack.sh new file mode 100755 index 0000000..ae77bda --- /dev/null +++ b/apps/base/artifact-keeper/resources/init-dtrack.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -e +DT_URL="${DEPENDENCY_TRACK_URL:-http://ak-artifact-keeper-dtrack:8080}" +DT_ADMIN_USER="admin" +DT_DEFAULT_PASS="admin" +DT_NEW_PASS="${DEPENDENCY_TRACK_ADMIN_PASSWORD}" +API_KEY_FILE="/shared/dtrack-api-key" + +echo "[dtrack-init] Waiting for Dependency-Track at $DT_URL ..." +for i in $(seq 1 60); do + if curl -sf "$DT_URL/api/version" > /dev/null 2>&1; then break; fi + if [ "$i" -eq 60 ]; then echo "[dtrack-init] ERROR: timeout"; exit 1; fi + sleep 5 +done + +if [ -f "$API_KEY_FILE" ] && [ -s "$API_KEY_FILE" ]; then + echo "[dtrack-init] API key already provisioned -- skipping" + exit 0 +fi + +TOKEN=$(curl -sf -X POST "$DT_URL/api/v1/user/login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=${DT_ADMIN_USER}&password=${DT_NEW_PASS}" 2>/dev/null || true) + +if [ -z "$TOKEN" ] || echo "$TOKEN" | grep -qi "FORCE_PASSWORD_CHANGE"; then + curl -sf -o /dev/null -X POST "$DT_URL/api/v1/user/forceChangePassword" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=${DT_ADMIN_USER}&password=${DT_DEFAULT_PASS}&newPassword=${DT_NEW_PASS}&confirmPassword=${DT_NEW_PASS}" + TOKEN=$(curl -sf -X POST "$DT_URL/api/v1/user/login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=${DT_ADMIN_USER}&password=${DT_NEW_PASS}" 2>/dev/null || true) +fi + +if [ -z "$TOKEN" ]; then echo "[dtrack-init] ERROR: auth failed"; exit 1; fi + +API_KEY=$(curl -sf "$DT_URL/api/v1/team" \ + -H "Authorization: Bearer $TOKEN" | \ + jq -r '.[] | select(.name == "Automation") | .apiKeys[0].key // empty') + +if [ -z "$API_KEY" ]; then echo "[dtrack-init] ERROR: no API key"; exit 1; fi + +echo "$API_KEY" > "$API_KEY_FILE" +echo "[dtrack-init] Done" diff --git a/apps/base/artifact-keeper/service_backend.yaml b/apps/base/artifact-keeper/service_backend.yaml new file mode 100644 index 0000000..673c26b --- /dev/null +++ b/apps/base/artifact-keeper/service_backend.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: backend +spec: + type: ClusterIP + ports: + - name: http + port: 8080 + targetPort: http + protocol: TCP + - name: grpc + port: 9090 + targetPort: grpc + protocol: TCP + selector: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: backend diff --git a/apps/base/artifact-keeper/service_dtrack.yaml b/apps/base/artifact-keeper/service_dtrack.yaml new file mode 100644 index 0000000..6888a9c --- /dev/null +++ b/apps/base/artifact-keeper/service_dtrack.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: dtrack + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: dependency-track +spec: + type: ClusterIP + ports: + - name: http + port: 8080 + targetPort: http + protocol: TCP + selector: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: dependency-track diff --git a/apps/base/artifact-keeper/service_meilisearch.yaml b/apps/base/artifact-keeper/service_meilisearch.yaml new file mode 100644 index 0000000..5cc744d --- /dev/null +++ b/apps/base/artifact-keeper/service_meilisearch.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: meilisearch + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: meilisearch +spec: + type: ClusterIP + ports: + - name: http + port: 7700 + targetPort: http + protocol: TCP + selector: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: meilisearch diff --git a/apps/base/artifact-keeper/service_trivy.yaml b/apps/base/artifact-keeper/service_trivy.yaml new file mode 100644 index 0000000..53973cd --- /dev/null +++ b/apps/base/artifact-keeper/service_trivy.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: trivy + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: trivy +spec: + type: ClusterIP + ports: + - name: http + port: 8090 + targetPort: http + protocol: TCP + selector: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: trivy diff --git a/apps/base/artifact-keeper/service_web.yaml b/apps/base/artifact-keeper/service_web.yaml new file mode 100644 index 0000000..aff671e --- /dev/null +++ b/apps/base/artifact-keeper/service_web.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: web + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: web +spec: + type: ClusterIP + ports: + - name: http + port: 3000 + targetPort: http + protocol: TCP + selector: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/component: web diff --git a/apps/base/artifact-keeper/serviceaccount_backend.yaml b/apps/base/artifact-keeper/serviceaccount_backend.yaml new file mode 100644 index 0000000..e81e72d --- /dev/null +++ b/apps/base/artifact-keeper/serviceaccount_backend.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: backend + namespace: artifact-keeper + labels: + app.kubernetes.io/name: artifact-keeper + app.kubernetes.io/instance: ak + app.kubernetes.io/part-of: artifact-keeper + app.kubernetes.io/component: backend diff --git a/apps/base/artifact-keeper/vaultauth.yaml b/apps/base/artifact-keeper/vaultauth.yaml new file mode 100644 index 0000000..e578e90 --- /dev/null +++ b/apps/base/artifact-keeper/vaultauth.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + name: default + namespace: artifact-keeper +spec: + allowedNamespaces: + - artifact-keeper + kubernetes: + audiences: + - vault + role: default + serviceAccount: default + tokenExpirationSeconds: 600 + method: kubernetes + mount: k8s/au/syd1 + vaultConnectionRef: vso-system/default diff --git a/apps/base/artifact-keeper/vaultstaticsecret.yaml b/apps/base/artifact-keeper/vaultstaticsecret.yaml new file mode 100644 index 0000000..5316361 --- /dev/null +++ b/apps/base/artifact-keeper/vaultstaticsecret.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: postgres-credentials + namespace: artifact-keeper +spec: + destination: + create: true + name: postgres-credentials + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/artifact-keeper/default/postgres-credentials + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: app-secrets + namespace: artifact-keeper +spec: + destination: + create: true + name: app-secrets + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/artifact-keeper/default/app-secrets + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: s3-credentials + namespace: artifact-keeper +spec: + destination: + create: true + name: s3-credentials + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/artifact-keeper/default/s3-credentials + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default diff --git a/apps/overlays/au-syd1/artifact-keeper/kustomization.yaml b/apps/overlays/au-syd1/artifact-keeper/kustomization.yaml new file mode 100644 index 0000000..699a884 --- /dev/null +++ b/apps/overlays/au-syd1/artifact-keeper/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/artifact-keeper