Add new infra architecture

This commit is contained in:
Urtzi Alfaro
2026-01-19 11:55:17 +01:00
parent 21d35ea92b
commit 35f164f0cd
311 changed files with 13241 additions and 3700 deletions

View File

@@ -0,0 +1,27 @@
# Create a root CA certificate for local development
# NOTE: This certificate must be ready before the local-ca-issuer can be used
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-ca-cert
namespace: cert-manager # This ensures the secret is created in the cert-manager namespace
spec:
isCA: true
commonName: bakery-ia-local-ca
subject:
organizationalUnits:
- "Bakery IA Local CA"
organizations:
- "Bakery IA"
countries:
- "US"
secretName: local-ca-key-pair
privateKey:
algorithm: ECDSA
size: 256
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
group: cert-manager.io
duration: 8760h # 1 year
renewBefore: 720h # 30 days

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
---
# NOTE: Do NOT define cert-manager ServiceAccounts here!
# The ServiceAccounts (cert-manager, cert-manager-cainjector, cert-manager-webhook)
# are created by the upstream cert-manager installation (kubernetes_restart.sh).
# Redefining them here would strip their RBAC bindings and break authentication.
---
# Self-signed ClusterIssuer for bootstrapping the CA certificate chain
# This issuer is used to create the root CA certificate which then
# becomes the issuer for all other certificates in the cluster
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
# Cert-manager installation using Helm repository
# This will be installed via kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml
# The actual installation will be done via command line, this file documents the resources

View File

@@ -0,0 +1,23 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
namespace: cert-manager
spec:
acme:
# The ACME server URL (Let's Encrypt production)
server: https://acme-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: admin@bakewise.ai
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-production
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: nginx
podTemplate:
spec:
nodeSelector:
"kubernetes.io/os": linux

View File

@@ -0,0 +1,24 @@
# Let's Encrypt Staging ClusterIssuer
# Use this for testing before switching to production
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# The ACME server URL (Let's Encrypt staging)
server: https://acme-staging-v02.api.letsencrypt.org/directory
# Email address used for ACME registration
email: admin@bakery-ia.local # Change this to your email
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-staging
# Enable the HTTP-01 challenge provider
solvers:
- http01:
ingress:
class: nginx
podTemplate:
spec:
nodeSelector:
"kubernetes.io/os": linux

View File

@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cert-manager.yaml
- ca-root-certificate.yaml
- local-ca-issuer.yaml
- cluster-issuer-staging.yaml
- cluster-issuer-production.yaml

View File

@@ -0,0 +1,7 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: local-ca-issuer
spec:
ca:
secretName: local-ca-key-pair

View File

@@ -0,0 +1,45 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: forecasting-service-hpa
namespace: bakery-ia
labels:
app.kubernetes.io/name: forecasting-service
app.kubernetes.io/component: autoscaling
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: forecasting-service
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 1
periodSeconds: 60
selectPolicy: Max

View File

@@ -0,0 +1,45 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: notification-service-hpa
namespace: bakery-ia
labels:
app.kubernetes.io/name: notification-service
app.kubernetes.io/component: autoscaling
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: notification-service
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 1
periodSeconds: 60
selectPolicy: Max

View File

@@ -0,0 +1,45 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: orders-service-hpa
namespace: bakery-ia
labels:
app.kubernetes.io/name: orders-service
app.kubernetes.io/component: autoscaling
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: orders-service
minReplicas: 1
maxReplicas: 3
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 1
periodSeconds: 60
selectPolicy: Max

View File

@@ -0,0 +1,106 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
namespace: bakery-ia
labels:
app.kubernetes.io/name: gateway
app.kubernetes.io/component: gateway
app.kubernetes.io/part-of: bakery-ia
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: gateway
app.kubernetes.io/component: gateway
template:
metadata:
labels:
app.kubernetes.io/name: gateway
app.kubernetes.io/component: gateway
spec:
imagePullSecrets:
- name: dockerhub-creds
containers:
- name: gateway
image: bakery/gateway:latest
ports:
- containerPort: 8000
name: http
envFrom:
- configMapRef:
name: bakery-config
- secretRef:
name: database-secrets
- secretRef:
name: redis-secrets
- secretRef:
name: rabbitmq-secrets
- secretRef:
name: jwt-secrets
- secretRef:
name: external-api-secrets
- secretRef:
name: payment-secrets
- secretRef:
name: email-secrets
- secretRef:
name: monitoring-secrets
- secretRef:
name: pos-integration-secrets
- secretRef:
name: whatsapp-secrets
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
valueFrom:
configMapKeyRef:
name: bakery-config
key: OTEL_EXPORTER_OTLP_ENDPOINT
- name: SIGNOZ_OTEL_COLLECTOR_URL
valueFrom:
configMapKeyRef:
name: bakery-config
key: SIGNOZ_OTEL_COLLECTOR_URL
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: gateway-service
namespace: bakery-ia
labels:
app.kubernetes.io/name: gateway
app.kubernetes.io/component: gateway
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 8000
protocol: TCP
name: http
selector:
app.kubernetes.io/name: gateway
app.kubernetes.io/component: gateway

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gateway-service.yaml
- nominatim/nominatim.yaml
- nominatim/nominatim-init-job.yaml

View File

@@ -0,0 +1,85 @@
apiVersion: batch/v1
kind: Job
metadata:
name: nominatim-init
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim-init
app.kubernetes.io/component: data-init
app.kubernetes.io/part-of: bakery-ia
spec:
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app.kubernetes.io/name: nominatim-init
app.kubernetes.io/component: data-init
spec:
imagePullSecrets:
- name: dockerhub-creds
restartPolicy: OnFailure
containers:
- name: nominatim-import
image: mediagis/nominatim:4.4
command:
- sh
- -c
- |
set -e
echo "Checking if Nominatim database is already initialized..."
if psql -lqt | cut -d \| -f 1 | grep -qw nominatim; then
echo "Nominatim database already exists. Skipping import."
exit 0
fi
echo "Downloading Spain OSM data..."
wget -O /tmp/spain-latest.osm.pbf "${NOMINATIM_PBF_URL}"
echo "Importing OSM data into Nominatim (this may take 30-60 minutes)..."
nominatim import --osm-file /tmp/spain-latest.osm.pbf
echo "Building search indices..."
nominatim refresh --website --importance
echo "Nominatim initialization complete!"
volumeMounts:
- name: nominatim-data
mountPath: /var/lib/postgresql
- name: nominatim-flatnode
mountPath: /nominatim-flatnode
env:
- name: NOMINATIM_PBF_URL
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_PBF_URL
- name: NOMINATIM_IMPORT_STYLE
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_IMPORT_STYLE
- name: NOMINATIM_THREADS
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_THREADS
- name: NOMINATIM_FLATNODE_FILE
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_FLATNODE_FILE
resources:
requests:
memory: "8Gi"
cpu: "4"
limits:
memory: "16Gi"
cpu: "8"
volumes:
- name: nominatim-data
persistentVolumeClaim:
claimName: nominatim-data
- name: nominatim-flatnode
persistentVolumeClaim:
claimName: nominatim-flatnode

View File

@@ -0,0 +1,158 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nominatim-config
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
data:
NOMINATIM_PBF_URL: "http://download.geofabrik.de/europe/spain-latest.osm.pbf"
NOMINATIM_REPLICATION_URL: "https://download.geofabrik.de/europe/spain-updates"
NOMINATIM_IMPORT_STYLE: "address"
NOMINATIM_THREADS: "4"
NOMINATIM_FLATNODE_FILE: "/nominatim-flatnode/flatnode.bin"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nominatim-data
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nominatim-flatnode
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nominatim
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
app.kubernetes.io/part-of: bakery-ia
spec:
serviceName: nominatim-service
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
template:
metadata:
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
spec:
containers:
- name: nominatim
image: mediagis/nominatim:4.4
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: nominatim-data
mountPath: /var/lib/postgresql
- name: nominatim-flatnode
mountPath: /nominatim-flatnode
env:
- name: NOMINATIM_PBF_URL
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_PBF_URL
- name: NOMINATIM_REPLICATION_URL
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_REPLICATION_URL
- name: NOMINATIM_IMPORT_STYLE
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_IMPORT_STYLE
- name: NOMINATIM_THREADS
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_THREADS
- name: NOMINATIM_FLATNODE_FILE
valueFrom:
configMapKeyRef:
name: nominatim-config
key: NOMINATIM_FLATNODE_FILE
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
livenessProbe:
httpGet:
path: /status
port: 8080
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /status
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5
volumes:
- name: nominatim-data
persistentVolumeClaim:
claimName: nominatim-data
- name: nominatim-flatnode
persistentVolumeClaim:
claimName: nominatim-flatnode
---
apiVersion: v1
kind: Service
metadata:
name: nominatim-service
namespace: bakery-ia
labels:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
spec:
selector:
app.kubernetes.io/name: nominatim
app.kubernetes.io/component: geocoding
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
type: ClusterIP

View File

@@ -0,0 +1,289 @@
# Mailu Email Infrastructure for Bakery-IA
This directory contains the Kubernetes deployment configuration for Mailu, a self-hosted email solution that integrates with external SMTP relays for optimal deliverability.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster (bakery-ia) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ notification- │ │ mail-service │ │ frontend │ │
│ │ service │─────▶│ (new/optional) │ │ │ │
│ │ │ │ Queue & Routing │ │ │ │
│ └────────┬─────────┘ └────────┬─────────┘ └──────────────────┘ │
│ │ │ │
│ │ SMTP (port 587) │ SMTP (port 587) │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ MAILU STACK │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ front │ │ admin │ │ smtp │ │ imap │ │ │
│ │ │ (nginx) │ │ (webmail) │ │ (postfix) │ │ (dovecot) │ │ │
│ │ │ :80/:443 │ │ :8080 │ │ :25/:587 │ │ :993/:143 │ │ │
│ │ └─────────────┘ └─────────────┘ └──────┬──────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ Relay │ │
│ │ │ antispam │ │ antivirus │ │ │ │
│ │ │ (rspamd) │ │ (clamav) │ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────┐ │ │ │
│ │ │ mailu-db (redis) │ │ │ │
│ │ └─────────────────────────────────┘ │ │ │
│ └───────────────────────────────────────────┼──────────────────────────┘ │
│ │ │
└──────────────────────────────────────────────┼───────────────────────────────┘
┌──────────────────────────────────────┐
│ EXTERNAL SMTP RELAY │
│ (SendGrid / Mailgun / AWS SES) │
│ │
│ • Handles IP reputation │
│ • Manages deliverability │
│ • Provides bounce/complaint hooks │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ INTERNET / RECIPIENTS │
└──────────────────────────────────────┘
```
## Components
### Core Services
- **mailu-front**: Nginx reverse proxy for web access (ports 80/443)
- **mailu-admin**: Web administration interface (port 80)
- **mailu-smtp**: Postfix SMTP server (ports 25/587)
- **mailu-imap**: Dovecot IMAP server (ports 143/993)
- **mailu-antispam**: Rspamd spam filtering (ports 11333/11334)
- **mailu-redis**: Redis for session management (port 6379)
### Storage
- **mailu-data**: 10Gi PVC for mail storage
- **mailu-db**: 5Gi PVC for database
- **mailu-redis**: 1Gi PVC for Redis persistence
## Configuration
### Environment Variables
The Mailu stack is configured via the `mailu-configmap.yaml` file:
- **DOMAIN**: `bakewise.ai`
- **HOSTNAMES**: `mail.bakewise.ai`
- **RELAYHOST**: `smtp.mailgun.org:587`
- **RELAY_LOGIN**: `apikey`
- **TLS_FLAVOR**: `cert` (uses Let's Encrypt)
- **WEBMAIL**: `roundcube`
- **ANTIVIRUS**: `clamav`
- **ANTISPAM**: `rspamd`
### Secrets
Secrets are managed in `mailu-secrets.yaml`:
- **ADMIN_PASSWORD**: Base64 encoded admin password
- **SECRET_KEY**: Mailu internal encryption key
- **RELAY_PASSWORD**: External SMTP relay API key
- **DB_PASSWORD**: Database password
- **REDIS_PASSWORD**: Redis password
## Deployment
### Prerequisites
1. Kubernetes cluster with storage provisioner
2. Ingress controller (NGINX)
3. Cert-manager for TLS certificates
4. External SMTP relay account (Mailgun, SendGrid, AWS SES)
### Deployment Steps
1. **Configure DNS**:
```bash
# MX record for inbound email
bakewise.ai. IN MX 10 mail.bakewise.ai.
# A record for mail server
mail.bakewise.ai. IN A <your-ingress-ip>
# SPF record (authorize external relay)
bakewise.ai. IN TXT "v=spf1 include:mailgun.org ~all"
# DKIM record (Mailu generates this)
mailu._domainkey.bakewise.ai. IN TXT "v=DKIM1; k=rsa; p=<public-key>"
# DMARC record
_dmarc.bakewise.ai. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@bakewise.ai"
```
2. **Update secrets**:
```bash
# Generate secure passwords
echo -n "your-secure-password" | base64
openssl rand -base64 32
# Update mailu-secrets.yaml with real values
```
3. **Deploy Mailu**:
```bash
# For production
kubectl apply -k infrastructure/environments/prod/k8s-manifests/
# For development
kubectl apply -k infrastructure/environments/dev/k8s-manifests/
```
4. **Verify deployment**:
```bash
kubectl get pods -n bakery-ia | grep mailu
kubectl logs -f mailu-smtp-<pod-id> -n bakery-ia
```
## Integration with Notification Service
The notification service has been updated to use Mailu as the SMTP server:
```yaml
# infrastructure/environments/common/configs/configmap.yaml
SMTP_HOST: "mailu-smtp.bakery-ia.svc.cluster.local"
SMTP_PORT: "587"
SMTP_TLS: "true"
SMTP_SSL: "false"
```
## Accessing Mailu
### Web Interface
- **Admin Panel**: `https://mail.bakewise.ai/admin`
- **Webmail**: `https://mail.bakewise.ai/webmail`
### SMTP Configuration
For external clients to send email through Mailu:
- **Server**: `mail.bakewise.ai`
- **Port**: 587 (Submission)
- **Security**: STARTTLS
- **Authentication**: Required
### IMAP Configuration
For email clients to access mailboxes:
- **Server**: `mail.bakewise.ai`
- **Port**: 993 (IMAPS)
- **Security**: SSL/TLS
- **Authentication**: Required
## Monitoring and Maintenance
### Health Checks
```bash
# Check Mailu services
kubectl get pods -n bakery-ia -l app=mailu
# Check Mailu logs
kubectl logs -f mailu-smtp-<pod-id> -n bakery-ia
kubectl logs -f mailu-antispam-<pod-id> -n bakery-ia
# Check queue status
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- mailq
```
### Backup and Restore
```bash
# Backup mail data
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- tar czf /backup/mailu-backup-$(date +%Y%m%d).tar.gz /data
# Restore mail data
kubectl cp mailu-backup-<date>.tar.gz mailu-smtp-<pod-id>:/backup/ -n bakery-ia
kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- tar xzf /backup/mailu-backup-<date>.tar.gz -C /
```
## Troubleshooting
### Common Issues
1. **SMTP Relay Authentication Failed**:
- Verify `RELAY_PASSWORD` in secrets matches your external relay API key
- Check network connectivity to external relay
2. **TLS Certificate Issues**:
- Ensure cert-manager is working properly
- Check DNS records are correctly pointing to your ingress
3. **Email Delivery Delays**:
- Check Mailu queue: `kubectl exec -it mailu-smtp-<pod-id> -n bakery-ia -- mailq`
- Verify external relay service status
4. **Spam Filtering Issues**:
- Check rspamd logs: `kubectl logs -f mailu-antispam-<pod-id> -n bakery-ia`
- Adjust spam scoring in rspamd configuration
## Resource Requirements
| Component | CPU Request | CPU Limit | Memory Request | Memory Limit | Storage |
|-----------|-------------|-----------|----------------|--------------|----------|
| mailu-front | 100m | 200m | 128Mi | 256Mi | - |
| mailu-admin | 100m | 300m | 256Mi | 512Mi | - |
| mailu-smtp | 100m | 500m | 256Mi | 512Mi | 10Gi |
| mailu-imap | 100m | 500m | 256Mi | 512Mi | - |
| mailu-antispam | 200m | 1000m | 512Mi | 1Gi | - |
| mailu-redis | 100m | 200m | 128Mi | 256Mi | 1Gi |
**Total**: ~600m CPU, ~1.7Gi Memory, 16Gi Storage
## Security Considerations
1. **Network Policies**: Mailu is protected by network policies that restrict access to only the notification service and ingress controller.
2. **TLS Encryption**: All external connections use TLS encryption.
3. **Authentication**: All services require authentication.
4. **Rate Limiting**: Configured to prevent abuse (60/hour per IP, 100/day per user).
5. **Spam Protection**: Rspamd provides comprehensive spam filtering with DKIM signing.
## Migration from External SMTP
To migrate from external SMTP (Gmail) to Mailu:
1. Update DNS records as shown above
2. Deploy Mailu stack
3. Update notification service configuration
4. Test email delivery
5. Monitor deliverability metrics
6. Gradually increase email volume
## External Relay Provider Comparison
| Provider | Pros | Cons | Free Tier |
|----------|------|------|-----------|
| SendGrid | Best deliverability, robust API | Expensive at scale | 100/day |
| Mailgun | Developer-friendly, good logs | EU data residency costs extra | 5,000/month (3 months) |
| AWS SES | Cheapest at scale ($0.10/1000) | Requires warm-up period | 62,000/month (from EC2) |
| Postmark | Transactional focus, fast | No marketing emails | 100/month |
**Recommendation**: AWS SES for cost-effectiveness and Kubernetes integration.
## Support
For issues with Mailu deployment:
1. Check the [Mailu documentation](https://mailu.io/)
2. Review Kubernetes events: `kubectl get events -n bakery-ia`
3. Check pod logs for specific components
4. Verify network connectivity and DNS resolution

View File

@@ -0,0 +1,265 @@
# Webmail DNS Configuration Guide
This guide provides the DNS configuration required to make the webmail system accessible from `webmail.bakewise.ai`.
## Production DNS Configuration
### Required DNS Records for `webmail.bakewise.ai`
```bash
# A Record for webmail subdomain
webmail.bakewise.ai. IN A <your-ingress-ip>
# CNAME Record (alternative approach)
webmail.bakewise.ai. IN CNAME bakewise.ai.
# MX Record for email delivery (if receiving emails)
bakewise.ai. IN MX 10 webmail.bakewise.ai.
# SPF Record (authorize webmail server)
bakewise.ai. IN TXT "v=spf1 include:mailgun.org ~all"
# DKIM Record (will be generated by Mailu)
mailu._domainkey.bakewise.ai. IN TXT "v=DKIM1; k=rsa; p=<public-key>"
# DMARC Record
_dmarc.bakewise.ai. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@bakewise.ai"
```
## Development DNS Configuration
### Required DNS Records for `webmail.bakery-ia.local`
For local development, add these entries to your `/etc/hosts` file:
```bash
# Add to /etc/hosts
127.0.0.1 webmail.bakery-ia.local
127.0.0.1 bakery-ia.local
127.0.0.1 monitoring.bakery-ia.local
```
## TLS Certificate Configuration
The ingress configuration includes automatic TLS certificate provisioning using cert-manager with Let's Encrypt.
### Production TLS Configuration
The production ingress (`prod-ingress.yaml`) includes:
```yaml
tls:
- hosts:
- bakewise.ai
- monitoring.bakewise.ai
- webmail.bakewise.ai # ← Added webmail domain
secretName: bakery-ia-prod-tls-cert
```
### Development TLS Configuration
The development ingress (`dev-ingress.yaml`) includes:
```yaml
tls:
- hosts:
- localhost
- bakery-ia.local
- monitoring.bakery-ia.local
- webmail.bakery-ia.local # ← Added webmail domain
secretName: bakery-dev-tls-cert
```
## Ingress Routing Configuration
### Production Routing
The production ingress routes traffic as follows:
- `https://bakewise.ai/` → Frontend service (port 3000)
- `https://bakewise.ai/api/` → Gateway service (port 8000)
- `https://monitoring.bakewise.ai/` → SigNoz monitoring (port 8080)
- `https://webmail.bakewise.ai/` → Email webmail (port 80)
- `https://webmail.bakewise.ai/webmail` → Email webmail
- `https://webmail.bakewise.ai/admin` → Email admin interface
### Development Routing
The development ingress routes traffic as follows:
- `https://localhost/` → Frontend service (port 3000)
- `https://localhost/api/` → Gateway service (port 8000)
- `https://bakery-ia.local/` → Frontend service (port 3000)
- `https://bakery-ia.local/api/` → Gateway service (port 8000)
- `https://monitoring.bakery-ia.local/` → SigNoz monitoring (port 8080)
- `https://webmail.bakery-ia.local/` → Email webmail (port 80)
- `https://webmail.bakery-ia.local/webmail` → Email webmail
- `https://webmail.bakery-ia.local/admin` → Email admin interface
## Security Headers
The webmail ingress includes enhanced security headers:
```nginx
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';
connect-src 'self'; frame-src 'self';
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```
## Deployment Steps
### 1. Update DNS Records
```bash
# For production (using Cloudflare as example)
cfcli dns create bakewise.ai A webmail <ingress-ip> --ttl 3600 --proxied
# For development (add to /etc/hosts)
echo "127.0.0.1 webmail.bakery-ia.local" | sudo tee -a /etc/hosts
```
### 2. Apply Ingress Configuration
```bash
# Apply the updated ingress configuration
kubectl apply -k infrastructure/environments/prod/k8s-manifests/
# Verify the ingress is configured correctly
kubectl get ingress -n bakery-ia
kubectl describe ingress bakery-ingress-prod -n bakery-ia
```
### 3. Verify TLS Certificates
```bash
# Check TLS certificate status
kubectl get certificaterequest -n bakery-ia
kubectl get certificate -n bakery-ia
# Check certificate details
kubectl describe certificate bakery-ia-prod-tls-cert -n bakery-ia
```
### 4. Test Webmail Access
```bash
# Test webmail accessibility
curl -I https://webmail.bakewise.ai
curl -I https://webmail.bakewise.ai/webmail
curl -I https://webmail.bakewise.ai/admin
# Test from browser
open https://webmail.bakewise.ai
```
## Troubleshooting
### DNS Issues
```bash
# Check DNS resolution
dig webmail.bakewise.ai
nslookup webmail.bakewise.ai
# Check ingress controller logs
kubectl logs -f -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
```
### TLS Issues
```bash
# Check cert-manager logs
kubectl logs -f -n cert-manager -l app=cert-manager
# Check certificate status
kubectl get certificaterequest,certificate,order,challenge -n bakery-ia
```
### Ingress Issues
```bash
# Check ingress controller events
kubectl get events -n ingress-nginx
# Check ingress description
kubectl describe ingress -n bakery-ia
```
## Monitoring and Maintenance
### Check Webmail Service Status
```bash
# Check email services
kubectl get pods -n bakery-ia -l app=email
# Check webmail service
kubectl get service email-webmail -n bakery-ia
# Check ingress routing
kubectl get ingress -n bakery-ia -o yaml | grep -A 10 webmail
```
### Update DNS Records
When the ingress IP changes, update the DNS records:
```bash
# Get current ingress IP
kubectl get service -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
# Update DNS (Cloudflare example)
cfcli dns update bakewise.ai A webmail <new-ip> --ttl 3600 --proxied
```
## Access Information
After configuration, the webmail system will be accessible at:
- **Production**: `https://webmail.bakewise.ai`
- **Development**: `https://webmail.bakery-ia.local`
Default credentials (configured in secrets):
- **Admin**: `admin@bakewise.ai`
- **Password**: Configured in `email-secrets`
## Integration with Existing Systems
The webmail system integrates with:
1. **SMTP Service**: `email-smtp.bakery-ia.svc.cluster.local:587`
2. **IMAP Service**: `email-imap.bakery-ia.svc.cluster.local:993`
3. **Notification Service**: Uses the new SMTP service for email notifications
4. **Monitoring**: SigNoz alerts use the new email service
## Backup and Recovery
### DNS Backup
```bash
# Export DNS records (Cloudflare example)
cfcli dns export bakewise.ai > dns-backup.json
# Restore DNS records
cfcli dns import bakewise.ai dns-backup.json
```
### Certificate Backup
```bash
# Export TLS secrets
kubectl get secret bakery-ia-prod-tls-cert -n bakery-ia -o yaml > tls-backup.yaml
# Restore TLS secrets
kubectl apply -f tls-backup.yaml
```
## References
- [Cert-manager Documentation](https://cert-manager.io/docs/)
- [NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/)
- [Let's Encrypt](https://letsencrypt.org/)
- [DNS Configuration Best Practices](https://www.cloudflare.com/learning/dns/)
This configuration provides a secure, scalable webmail solution that integrates seamlessly with the existing Bakery-IA infrastructure.

View File

@@ -0,0 +1,24 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bakery-ia
resources:
- mailu-configmap.yaml
- mailu-secrets.yaml
- mailu-pvc.yaml
- mailu-deployment.yaml
- mailu-services.yaml
- mailu-antispam.yaml
- mailu-networkpolicy.yaml
# NOTE: mailu-ingress.yaml removed - ingress is now centralized in platform/networking
# NOTE: mailu-replacement.yaml removed - using official Mailu stack
# NOTE: email-config.yaml removed - configuration consolidated into mailu-configmap.yaml
# NOTE: Network policy kept here for self-contained module (could be moved to global security)
# NOTE: Mailu uses shared Redis (redis-service) with database 15 - no separate Redis needed
labels:
- includeSelectors: true
pairs:
app: mailu
platform: mail
managed-by: kustomize

View File

@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: antispam
template:
metadata:
labels:
app: mailu
component: antispam
spec:
containers:
- name: antispam
image: ghcr.io/mailu/rspamd:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11333
name: rspamd
- containerPort: 11334
name: rspamd-admin
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -0,0 +1,79 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mailu-config
namespace: bakery-ia
labels:
app: mailu
component: config
data:
# Domain configuration
DOMAIN: "bakewise.ai"
HOSTNAMES: "mail.bakewise.ai"
POSTMASTER: "admin"
# Kubernetes-specific settings
# These help Mailu components discover each other in K8s
FRONT_ADDRESS: "mailu-front.bakery-ia.svc.cluster.local"
ADMIN_ADDRESS: "mailu-admin.bakery-ia.svc.cluster.local"
SMTP_ADDRESS: "mailu-smtp.bakery-ia.svc.cluster.local"
IMAP_ADDRESS: "mailu-imap.bakery-ia.svc.cluster.local"
ANTISPAM_ADDRESS: "mailu-antispam.bakery-ia.svc.cluster.local"
# Redis Configuration - Using shared cluster Redis (database 15 reserved for Mailu)
# The shared Redis has 16 databases (0-15), Mailu uses db 15 for isolation
# Using plain TCP port 6380 for internal cluster communication (TLS on 6379 for external)
# Primary configuration: Redis URL is configured in mailu-secrets.yaml as REDIS_URL
# Format: redis://:password@host:port/db
# Fallback configuration: REDIS_ADDRESS, REDIS_DB, and REDIS_PW
REDIS_ADDRESS: "redis-service.bakery-ia.svc.cluster.local:6380"
REDIS_DB: "15"
# REDIS_PW is set from secrets for Redis authentication
# External SMTP Relay Configuration
# Mailu relays outbound emails through an external service for better deliverability
# Supported providers: Mailgun, SendGrid, AWS SES, Postmark
#
# Provider RELAYHOST examples:
# Mailgun: [smtp.mailgun.org]:587
# SendGrid: [smtp.sendgrid.net]:587
# AWS SES: [email-smtp.us-east-1.amazonaws.com]:587
# Postmark: [smtp.postmarkapp.com]:587
#
# IMPORTANT: Update RELAY_PASSWORD in mailu-secrets.yaml with your provider's API key
RELAYHOST: "[smtp.mailgun.org]:587"
RELAY_LOGIN: "postmaster@bakewise.ai"
# Security settings
TLS_FLAVOR: "cert"
AUTH_RATELIMIT_IP: "60/hour"
AUTH_RATELIMIT_USER: "100/day"
# Message limits
MESSAGE_SIZE_LIMIT: "52428800" # 50MB
MESSAGE_RATELIMIT: "200/day"
# Features - disable ClamAV in dev to save resources (enable in prod)
WEBMAIL: "roundcube"
ANTIVIRUS: "none"
ANTISPAM: "rspamd"
# Postfix configuration
POSTFIX_MESSAGE_SIZE_LIMIT: "52428800"
POSTFIX_QUEUE_MINIMUM: "1"
POSTFIX_QUEUE_LIFETIME: "7d"
# DKIM configuration
DKIM_SELECTOR: "mailu"
DKIM_KEY_LENGTH: "2048"
# Webmail settings
WEB_WEBMAIL: "/webmail"
WEB_ADMIN: "/admin"
WEBMAIL_ADMIN: "admin@bakewise.ai"
# Logging
LOG_LEVEL: "INFO"
# Disable welcome email during development
WELCOME: "false"

View File

@@ -0,0 +1,208 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: front
template:
metadata:
labels:
app: mailu
component: front
spec:
containers:
- name: front
image: ghcr.io/mailu/nginx:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
- name: mailu-tls
mountPath: /certs
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
- name: mailu-tls
secret:
# TLS secret name is environment-specific:
# - Dev: bakery-dev-tls-cert (self-signed, from dev-certificate.yaml)
# - Prod: bakery-ia-prod-tls-cert (Let's Encrypt, from prod-certificate.yaml)
# Patched via kustomize overlays in dev/prod kustomization.yaml
secretName: MAILU_TLS_SECRET_PLACEHOLDER
items:
- key: tls.crt
path: cert.pem
- key: tls.key
path: key.pem
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: admin
template:
metadata:
labels:
app: mailu
component: admin
spec:
containers:
- name: admin
image: ghcr.io/mailu/admin:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: smtp
template:
metadata:
labels:
app: mailu
component: smtp
spec:
containers:
- name: smtp
image: ghcr.io/mailu/postfix:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 25
name: smtp
- containerPort: 587
name: submission
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
replicas: 1
selector:
matchLabels:
app: mailu
component: imap
template:
metadata:
labels:
app: mailu
component: imap
spec:
containers:
- name: imap
image: ghcr.io/mailu/dovecot:2024.06
imagePullPolicy: IfNotPresent
ports:
- containerPort: 143
name: imap
- containerPort: 993
name: imaps
envFrom:
- configMapRef:
name: mailu-config
- secretRef:
name: mailu-secrets
volumeMounts:
- name: mailu-data
mountPath: /data
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mailu-data
persistentVolumeClaim:
claimName: mailu-data

View File

@@ -0,0 +1,93 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mailu-network-policy
namespace: bakery-ia
labels:
app: mailu
component: network-policy
spec:
# Apply to all Mailu pods (matches mailu-deployment.yaml labels)
podSelector:
matchLabels:
app: mailu
policyTypes:
- Ingress
- Egress
ingress:
# Allow SMTP from notification-service
- from:
- podSelector:
matchLabels:
app: notification-service
ports:
- port: 25
- port: 587
# Allow SMTP from other internal services that may need to send email
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: bakery-ia
ports:
- port: 587
# Allow webmail/admin access via ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 80
- port: 443
# Allow internal Mailu component communication
- from:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
egress:
# Allow relay to external SMTP (Mailgun)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- port: 587
- port: 465
- port: 25
# Allow internal Mailu component communication
- to:
- podSelector:
matchLabels:
app: mailu
ports:
- port: 25
- port: 587
- port: 143
- port: 993
- port: 80
- port: 11333
- port: 11334
# Allow connection to shared Redis (database 15)
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: redis
ports:
- port: 6379
# Allow DNS lookups
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View File

@@ -0,0 +1,21 @@
# Mailu data storage - shared across all Mailu components
# Contains: mail data, SQLite database, DKIM keys, SSL certificates, queue
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mailu-data
namespace: bakery-ia
labels:
app: mailu
component: storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# NOTE: Change storageClassName based on your cluster's storage provisioner
# For local development (kind): standard
# For AWS EKS: gp2 or gp3
# For GKE: standard or premium-rwo
# For AKS: managed-premium or managed-csi

View File

@@ -0,0 +1,37 @@
apiVersion: v1
kind: Secret
metadata:
name: mailu-secrets
namespace: bakery-ia
labels:
app: mailu
component: secrets
type: Opaque
data:
# Admin credentials (base64 encoded)
# IMPORTANT: Replace with real credentials before production deployment
# Generate with: openssl rand -base64 24 | tr -d '\n' | base64
ADMIN_PASSWORD: "VzJYS2tSdUxpT25ZS2RCWVFTQXJvbjFpeWtFU1M1b2I=" # W2XKkRuLiOnYKdBYQSAron1iykESS5ob
# Mailu secret key for internal encryption
# Generate with: openssl rand -base64 32
SECRET_KEY: "Y2I2MWI5MzRkNDcwMjlhNjQxMTdjMGU0MTEwYzkzZjY2YmJjZjVlYWExNWM4NGM0MjcyN2ZhZDc4Zjc=" # cb61b934d47029a64117c0e4110c93f66bbcf5eaa15c84c42727fad78f7
# External SMTP relay credentials (Mailgun)
# For Mailgun: use postmaster@domain as username
RELAY_USER: "cG9zdG1hc3RlckBiYWtld2lzZS5haQ==" # postmaster@bakewise.ai
RELAY_PASSWORD: "bWFpbGd1bi1hcGkta2V5LXJlcGxhY2UtaW4tcHJvZHVjdGlvbg==" # mailgun-api-key-replace-in-production
# Database credentials
DB_PASSWORD: "RThLejQ3WW1WekRsSEdzMU05d0FiSnp4Y0tuR09OQ1Q=" # E8Kz47YmVzDlHGs1M9wAbJzxcKnGONCT
# Dovecot admin password (moved from ConfigMap for security)
DOVEADM_PASSWORD: "WnZhMzNoaVBJc2ZtV3RxUlBWV29taTRYZ2xLTlZPcHY=" # Zva33hiPIsfmWtqRPVWomi4XglKNVOpv
# Redis password - same as shared cluster Redis (redis-secrets)
# Mailu uses database 15 for isolation from other services
# REDIS_PW is required by Mailu for Redis authentication
REDIS_PASSWORD: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
REDIS_PW: "SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0E=" # J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A
# Redis URL for Mailu - using plain TCP port 6380 for internal cluster communication
REDIS_URL: "cmVkaXM6Ly86SjNsa2x4cHU5QzlPTElLdkJteFVIT2h0czFnc0lvM0FAcmVkaXMtc2VydmljZS5iYWtlcnktaWEuc3ZjLmNsdXN0ZXIubG9jYWw6NjM4MC8xNQ==" # redis://:J3lklxpu9C9OLIKvBmxUHOhts1gsIo3A@redis-service.bakery-ia.svc.cluster.local:6380/15

View File

@@ -0,0 +1,126 @@
# Mailu Services - Routes traffic to Mailu stack components
# All services use app: mailu selectors to match mailu-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: mailu-front
namespace: bakery-ia
labels:
app: mailu
component: front
spec:
type: ClusterIP
selector:
app: mailu
component: front
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
---
apiVersion: v1
kind: Service
metadata:
name: mailu-admin
namespace: bakery-ia
labels:
app: mailu
component: admin
spec:
type: ClusterIP
selector:
app: mailu
component: admin
ports:
- name: http
port: 80
targetPort: 80
---
# Primary SMTP service - used by notification-service and other internal services
apiVersion: v1
kind: Service
metadata:
name: mailu-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
# Alias for backwards compatibility with services expecting 'email-smtp'
apiVersion: v1
kind: Service
metadata:
name: email-smtp
namespace: bakery-ia
labels:
app: mailu
component: smtp
spec:
type: ClusterIP
selector:
app: mailu
component: smtp
ports:
- name: smtp
port: 25
targetPort: 25
- name: submission
port: 587
targetPort: 587
---
apiVersion: v1
kind: Service
metadata:
name: mailu-imap
namespace: bakery-ia
labels:
app: mailu
component: imap
spec:
type: ClusterIP
selector:
app: mailu
component: imap
ports:
- name: imap
port: 143
targetPort: 143
- name: imaps
port: 993
targetPort: 993
---
apiVersion: v1
kind: Service
metadata:
name: mailu-antispam
namespace: bakery-ia
labels:
app: mailu
component: antispam
spec:
type: ClusterIP
selector:
app: mailu
component: antispam
ports:
- name: rspamd
port: 11333
targetPort: 11333
- name: rspamd-admin
port: 11334
targetPort: 11334

View File

@@ -0,0 +1,92 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bakery-ingress
namespace: bakery-ia
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: ingress
annotations:
# Nginx ingress controller annotations
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
# SSE and WebSocket configuration for long-lived connections
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "3600"
# WebSocket upgrade support
nginx.ingress.kubernetes.io/websocket-services: "gateway-service"
# CORS configuration
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS, PATCH"
nginx.ingress.kubernetes.io/cors-allow-headers: "Content-Type, Authorization, X-Requested-With, Accept, Origin, Cache-Control"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- DOMAIN_PLACEHOLDER # To be replaced by kustomize
- gitea.DOMAIN_PLACEHOLDER # To be replaced by kustomize
- mail.DOMAIN_PLACEHOLDER # To be replaced by kustomize
secretName: TLS_SECRET_PLACEHOLDER # To be replaced by kustomize
rules:
# Main application routes
- host: DOMAIN_PLACEHOLDER # To be replaced by kustomize
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 3000
- path: /api
pathType: Prefix
backend:
service:
name: gateway-service
port:
number: 8000
# Gitea CI/CD route
- host: gitea.DOMAIN_PLACEHOLDER # To be replaced by kustomize
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea-http
port:
number: 3000
# Mail server web interface (webmail and admin)
- host: mail.DOMAIN_PLACEHOLDER # To be replaced by kustomize
http:
paths:
- path: /webmail
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 80
- path: /admin
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: mailu-front
port:
number: 80

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ingress.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- base/

View File

@@ -0,0 +1,37 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
patches:
- target:
kind: Ingress
name: bakery-ingress
patch: |-
- op: replace
path: /spec/tls/0/hosts/0
value: bakery-ia.local
- op: replace
path: /spec/tls/0/hosts/1
value: gitea.bakery-ia.local
- op: replace
path: /spec/tls/0/hosts/2
value: mail.bakery-ia.local
- op: replace
path: /spec/tls/0/secretName
value: bakery-dev-tls-cert
- op: replace
path: /spec/rules/0/host
value: bakery-ia.local
- op: replace
path: /spec/rules/1/host
value: gitea.bakery-ia.local
- op: replace
path: /spec/rules/2/host
value: mail.bakery-ia.local
- op: replace
path: /metadata/annotations/nginx.ingress.kubernetes.io~1cors-allow-origin
value: "https://localhost,https://localhost:3000,https://localhost:3001,https://127.0.0.1,https://127.0.0.1:3000,https://127.0.0.1:3001,https://bakery-ia.local,http://localhost,http://localhost:3000,http://localhost:3001,http://127.0.0.1,http://127.0.0.1:3000"

View File

@@ -0,0 +1,49 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
patches:
- target:
kind: Ingress
name: bakery-ingress
patch: |-
- op: replace
path: /spec/tls/0/hosts/0
value: bakewise.ai
- op: replace
path: /spec/tls/0/hosts/1
value: gitea.bakewise.ai
- op: replace
path: /spec/tls/0/hosts/2
value: mail.bakewise.ai
- op: replace
path: /spec/tls/0/secretName
value: bakery-ia-prod-tls-cert
- op: replace
path: /spec/rules/0/host
value: bakewise.ai
- op: replace
path: /spec/rules/1/host
value: gitea.bakewise.ai
- op: replace
path: /spec/rules/2/host
value: mail.bakewise.ai
- op: add
path: /metadata/annotations/nginx.ingress.kubernetes.io~1cors-allow-origin
value: "https://bakewise.ai,https://www.bakewise.ai,https://mail.bakewise.ai"
- op: add
path: /metadata/annotations/nginx.ingress.kubernetes.io~1limit-rps
value: "100"
- op: add
path: /metadata/annotations/nginx.ingress.kubernetes.io~1limit-connections
value: "50"
- op: add
path: /metadata/annotations/cert-manager.io~1cluster-issuer
value: "letsencrypt-production"
- op: add
path: /metadata/annotations/cert-manager.io~1acme-challenge-type
value: "http01"

View File

@@ -0,0 +1,55 @@
# Kubernetes Secrets Encryption
This directory contains configuration for encrypting Kubernetes secrets at rest.
## What is this for?
Kubernetes secrets are stored in etcd, and by default they are stored as plaintext. This encryption configuration ensures that secrets are encrypted when stored in etcd, providing an additional layer of security.
## Files
- `encryption-config.yaml` - Main encryption configuration file
## How it works
1. The API server uses this configuration to encrypt secrets before storing them in etcd
2. When secrets are retrieved, they are automatically decrypted by the API server
3. This provides encryption at rest for all Kubernetes secrets
## Security Notes
- The encryption key is stored in this file (base64 encoded)
- This file should be protected and not committed to version control in production
- For development, this provides basic encryption at rest
- In production, consider using a proper key management system
## Generating a new key
```bash
openssl rand -base64 32
```
## Configuration Details
- **Algorithm**: AES-CBC with 256-bit keys
- **Provider**: `aescbc` - AES-CBC encryption provider
- **Fallback**: `identity` - Allows reading unencrypted secrets during migration
## Usage
This configuration is automatically used by the Kind cluster configuration in `kind-config.yaml`. The file is mounted into the Kubernetes control plane container and referenced by the API server configuration.
## Rotation
To rotate keys:
1. Add a new key to the `keys` array
2. Make the new key the first in the array
3. Restart the API server
4. Old keys can be removed after all secrets have been re-encrypted with the new key
## Compliance
This encryption helps satisfy:
- GDPR Article 32 - Security of processing
- PCI DSS Requirement 3.4 - Encryption of sensitive data
- ISO 27001:2022 - Cryptographic controls

View File

@@ -0,0 +1,17 @@
# Kubernetes Secrets Encryption Configuration
# This file configures encryption at rest for Kubernetes secrets
# Used by the API server to encrypt secret data stored in etcd
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
# 32-byte (256-bit) AES key encoded in base64
# Generated using: openssl rand -base64 32
secret: 62um3zP5aidjVSIB0ckAxF/Ms8EDy/Z8LyMGTdMuoSM=
- identity: {}

View File

@@ -0,0 +1,108 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: bakery-ia
labels:
app: global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-kube-dns
namespace: bakery-ia
labels:
app: global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# Allow DNS resolution to kube-system namespace
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: bakery-ia
labels:
app: global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
# This label should match your ingress controller's namespace
# Adjust as needed for your specific ingress controller
app: nginx-ingress-microk8s
policyTypes:
- Ingress
ingress:
# Allow all traffic to ingress controller
- from:
- ipBlock:
cidr: 0.0.0.0/0
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-internal-communication
namespace: bakery-ia
labels:
app: global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# Allow communication between pods in the same namespace
- from:
- podSelector: {}
egress:
# Allow communication to pods in the same namespace
- to:
- podSelector: {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-external
namespace: bakery-ia
labels:
app: global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
app: external-egress-allowed
policyTypes:
- Egress
egress:
# Allow external communication for services that need it
- to:
- ipBlock:
cidr: 0.0.0.0/0

View File

@@ -0,0 +1,159 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-default-deny
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-dns
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# Allow DNS resolution to kube-system namespace
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-ingress-access
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
policyTypes:
- Ingress
ingress:
# Allow all traffic to ingress controller
- from:
- ipBlock:
cidr: 0.0.0.0/0
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-internal-comm
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# Allow communication between project services
- from:
- namespaceSelector:
matchLabels:
name: bakery-ia
egress:
# Allow communication to project services
- to:
- namespaceSelector:
matchLabels:
name: bakery-ia
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-monitoring
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
app: signoz
policyTypes:
- Ingress
ingress:
# Allow monitoring access from project services
- from:
- namespaceSelector:
matchLabels:
name: bakery-ia
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-database-access
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
# Allow database access from application services
- from:
- namespaceSelector:
matchLabels:
name: bakery-ia
ports:
- port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: project-allow-cache-access
namespace: bakery-ia
labels:
app: project-global
component: network-policy
tier: security
spec:
podSelector:
matchLabels:
app: redis
policyTypes:
- Ingress
ingress:
# Allow cache access from application services
- from:
- namespaceSelector:
matchLabels:
name: bakery-ia
ports:
- port: 6379

View File

@@ -0,0 +1,19 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
# Storage infrastructure
- minio/minio-deployment.yaml
- minio/minio-pvc.yaml
- minio/minio-secrets.yaml
- minio/minio-bucket-init-job.yaml
- minio/secrets/minio-tls-secret.yaml
# Cache infrastructure
- redis/redis.yaml
- redis/secrets/redis-tls-secret.yaml
# Database infrastructure
- postgres/secrets/postgres-tls-secret.yaml
- postgres/configs/postgres-logging-config.yaml
- postgres/configs/postgres-init-config.yaml

View File

@@ -0,0 +1,193 @@
apiVersion: batch/v1
kind: Job
metadata:
name: minio-bucket-init
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio-bucket-init
app.kubernetes.io/component: storage-init
app.kubernetes.io/part-of: bakery-ia
spec:
ttlSecondsAfterFinished: 300
backoffLimit: 3
template:
metadata:
labels:
app.kubernetes.io/name: minio-bucket-init
app.kubernetes.io/component: storage-init
spec:
restartPolicy: OnFailure
initContainers:
# Wait for MinIO to be ready
- name: wait-for-minio
image: busybox:1.36
command:
- sh
- -c
- |
echo "Waiting for MinIO to be ready..."
until nc -z minio.bakery-ia.svc.cluster.local 9000; do
echo "MinIO not ready, waiting..."
sleep 5
done
echo "MinIO is ready!"
containers:
- name: bucket-init
image: minio/mc:RELEASE.2024-11-17T19-35-25Z
command:
- /bin/sh
- -c
- |
set -e
echo "Configuring MinIO client..."
# Configure mc alias with TLS (skip cert verification for self-signed)
mc alias set myminio https://minio.bakery-ia.svc.cluster.local:9000 \
${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} --insecure
echo "Creating buckets..."
# Create training-models bucket if not exists
if ! mc ls myminio/training-models --insecure 2>/dev/null; then
mc mb myminio/training-models --insecure
echo "Created bucket: training-models"
else
echo "Bucket already exists: training-models"
fi
# Set bucket policy (private by default)
mc anonymous set none myminio/training-models --insecure
# Enable versioning for model backups
mc version enable myminio/training-models --insecure
echo "Enabled versioning on training-models bucket"
# Set lifecycle policy to expire old versions after 90 days
cat > /tmp/lifecycle.json << 'EOF'
{
"Rules": [
{
"ID": "expire-old-versions",
"Status": "Enabled",
"Filter": {
"Prefix": "models/"
},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 90
}
},
{
"ID": "expire-old-metadata",
"Status": "Enabled",
"Filter": {
"Prefix": "models/"
},
"Expiration": {
"ExpiredObjectDeleteMarker": true
}
}
]
}
EOF
mc ilm import myminio/training-models < /tmp/lifecycle.json --insecure || true
echo "Lifecycle policy configured"
# Create service accounts with limited permissions
echo "Creating service accounts..."
# Training service policy (read/write models)
cat > /tmp/training-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": [
"arn:aws:s3:::training-models",
"arn:aws:s3:::training-models/*"
]
}
]
}
EOF
# Forecasting service policy (read-only models)
cat > /tmp/forecasting-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::training-models",
"arn:aws:s3:::training-models/*"
]
}
]
}
EOF
# Create service accounts using credentials from secrets
echo "Creating service accounts..."
mc admin user add myminio ${TRAINING_MINIO_USER} ${TRAINING_MINIO_PASSWORD} --insecure 2>/dev/null || true
mc admin user add myminio ${FORECASTING_MINIO_USER} ${FORECASTING_MINIO_PASSWORD} --insecure 2>/dev/null || true
# Apply policies (ignore errors if already exists)
mc admin policy create myminio training-policy /tmp/training-policy.json --insecure 2>/dev/null || true
mc admin policy attach myminio training-policy --user=${TRAINING_MINIO_USER} --insecure 2>/dev/null || true
mc admin policy create myminio forecasting-policy /tmp/forecasting-policy.json --insecure 2>/dev/null || true
mc admin policy attach myminio forecasting-policy --user=${FORECASTING_MINIO_USER} --insecure 2>/dev/null || true
echo "MinIO bucket initialization complete!"
# List buckets for verification
echo "Current buckets:"
mc ls myminio --insecure
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_ROOT_USER
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_ROOT_PASSWORD
# Training service MinIO credentials
- name: TRAINING_MINIO_USER
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_ACCESS_KEY
- name: TRAINING_MINIO_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_SECRET_KEY
# Forecasting service MinIO credentials
- name: FORECASTING_MINIO_USER
valueFrom:
secretKeyRef:
name: minio-secrets
key: FORECASTING_MINIO_ACCESS_KEY
- name: FORECASTING_MINIO_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secrets
key: FORECASTING_MINIO_SECRET_KEY

View File

@@ -0,0 +1,154 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
app.kubernetes.io/part-of: bakery-ia
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
template:
metadata:
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
spec:
# Init container to set up TLS certificates with correct permissions
initContainers:
- name: init-certs
image: busybox:1.36
command:
- sh
- -c
- |
mkdir -p /certs/CAs
cp /certs-secret/minio-cert.pem /certs/public.crt
cp /certs-secret/minio-key.pem /certs/private.key
cp /certs-secret/ca-cert.pem /certs/CAs/ca.crt
chmod 600 /certs/private.key
chmod 644 /certs/public.crt /certs/CAs/ca.crt
volumeMounts:
- name: certs-secret
mountPath: /certs-secret
readOnly: true
- name: certs
mountPath: /certs
containers:
- name: minio
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
args:
- server
- /data
- --console-address
- :9001
- --address
- :9000
- --certs-dir
- /certs
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_ROOT_USER
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-secrets
key: MINIO_ROOT_PASSWORD
# Enable TLS for MinIO
- name: MINIO_SERVER_URL
value: "https://minio.bakery-ia.svc.cluster.local:9000"
- name: MINIO_BROWSER_REDIRECT_URL
value: "https://minio-console.bakery-ia.svc.cluster.local:9001"
ports:
- containerPort: 9000
name: api
- containerPort: 9001
name: console
volumeMounts:
- name: minio-data
mountPath: /data
- name: certs
mountPath: /certs
readOnly: true
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /minio/health/live
port: 9000
scheme: HTTPS
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /minio/health/ready
port: 9000
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 15
volumes:
- name: minio-data
persistentVolumeClaim:
claimName: minio-data
- name: certs-secret
secret:
secretName: minio-tls
- name: certs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: minio
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
spec:
type: ClusterIP
ports:
- port: 9000
targetPort: 9000
protocol: TCP
name: api
- port: 9001
targetPort: 9001
protocol: TCP
name: console
selector:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
---
apiVersion: v1
kind: Service
metadata:
name: minio-console
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage
spec:
type: ClusterIP
ports:
- port: 9001
targetPort: 9001
protocol: TCP
name: console
selector:
app.kubernetes.io/name: minio
app.kubernetes.io/component: storage

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-data
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio-data
app.kubernetes.io/component: storage
app.kubernetes.io/part-of: bakery-ia
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: standard

View File

@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: minio-secrets
namespace: bakery-ia
labels:
app.kubernetes.io/name: minio-secrets
app.kubernetes.io/component: storage
app.kubernetes.io/part-of: bakery-ia
type: Opaque
data:
# MinIO Root Credentials (base64 encoded)
MINIO_ROOT_USER: YWRtaW4= # admin
MINIO_ROOT_PASSWORD: c2VjdXJlLXBhc3N3b3Jk # secure-password
# Service Account Credentials for applications
MINIO_ACCESS_KEY: dHJhaW5pbmctc2VydmljZQ== # training-service
MINIO_SECRET_KEY: dHJhaW5pbmctc2VjcmV0LWtleQ== # training-secret-key
# Forecasting Service Credentials
FORECASTING_MINIO_ACCESS_KEY: Zm9yZWNhc3Rpbmctc2VydmljZQ== # forecasting-service
FORECASTING_MINIO_SECRET_KEY: Zm9yZWNhc3Rpbmctc2VjcmV0LWtleQ== # forecasting-secret-key

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: Secret
metadata:
name: minio-tls
namespace: bakery-ia
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: minio-tls
app.kubernetes.io/part-of: bakery-ia
type: Opaque
data:
# MinIO TLS certificates (base64 encoded)
# Generated using infrastructure/tls/generate-minio-certificates.sh
# Valid for 3 years from generation date
#
# Certificate details:
# Subject: CN=minio.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Storage
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
#
# To regenerate:
# 1. Run: infrastructure/tls/generate-minio-certificates.sh
# 2. Run: scripts/create-tls-secrets.sh
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
minio-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdyVENDQkpXZ0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGdBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOakF4TVRjeE5EVTBORGhhRncweU9UQXgKTVRZeE5EVTBORGhhTUlHS01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFUU1BNEdBMVVFCkN3d0hVM1J2Y21GblpURXFNQ2dHQTFVRUF3d2hiV2x1YVc4dVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnkKTG14dlkyRnNNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQW5qdTd0cFF3dkYvVgprL011UmhySllWME1KcXRyRkovTlgrMU9MSmFNaEZYL0tZMTBMUCtCNjV3L3BsWkd5SnRidFVkV2o1d1pMclpRCm1KYjNwNFR0dUs0QlQxZ3UzYlNaS0lIUU5lQWc4MUtzTUdxKzV1WE9vUFdOckFoaDRoWU9KNDVtSXNZYmEwRGQKTzJNRnY5V3VXVm4zVDZGenpNN3FMZENKelpOamVhQjdtVEpqZEhHcjg0aVQ4NkFFQStIeXd2c3FPb2paZStVagpLdThYcmp4VUdSL2VQRnZRQ3lNZFdnRmJqd2lqSi9CbjhSQ0FSSXVpRXNzalNMUVdPZ1FncklBVHZFRi9jeVVkClpLR2hhYzMvNEk3MXhEV2hYNzFYV1l3T05FbXJRNmNHelhtdmNVTVY4SHZFV016YjA1UnBPWXp5bUtyYnhOTDQKZVdOYUt2cnZjWnpjTXpwSU00UmVHS3cyTjlzQUdzM1lCVFI3V1hMS1dnbkxZYnNvSHgzZGRadXlRK0hKd0RUWApxcFh1dFloYW9DZmZIMjNuTU1GaUFLMWltZWJCSTFoVWNBaVB2cFN4N2RJM21nTlA0YWZOL29xaE1PUGc4VHhtCndNZWt2cHovN2NXYkNPTmprZDlkcTBWTExTVyt0cUlmZlZRajBMT1VQdlhyTE9tUG1jTDZsU2xSTzg4NVRWdngKSkRidDJYVVJtaHFKenBhcklmTmhGOUVscEhtYnNkc2xtWVBvLzlKV1VtcmtiSjZBYWZkbEpuckNUR3hKcGl3TAowbEpveEl3dnFZdDhEQnVjMWNORktKSVNMWkl5bzZ1WFJ1TlZvTnByeGdmVXZsOENscDNnUyttSVNGZzMzdTJrCkpjYnF6bnZ2YzN0YmxIZTB4ZzJNSE1JVlRkWmlSamNDQXdFQUFhT0NBUjB3Z2dFWk1Bc0dBMVVkRHdRRUF3SUUKTURBZEJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdnYW9HQTFVZEVRU0JvakNCbjRJaApiV2x1YVc4dVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2dnOXRhVzVwYnk1aVlXdGxjbmt0CmFXR0NLVzFwYm1sdkxXTnZibk52YkdVdVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2doZHQKYVc1cGJ5MWpiMjV6YjJ4bExtSmhhMlZ5ZVMxcFlZSUZiV2x1YVcrQ0RXMXBibWx2TFdOdmJuTnZiR1dDQ1d4dgpZMkZzYUc5emRJY0Vmd0FBQVRBZEJnTlZIUTRFRmdRVXJXMzNxOWkreE5MdVZjcGUrKzlxUE56dVF4VXdId1lEClZSMGpCQmd3Rm9BVUErNnEva2M4ZlRRVTFFRHF6R1JmS1FwcTZtMHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUIKQUlTT0NieFJWd2xtaWdjNldLM3hUaUJxNlJGMGNzdnV5NjJNYnI3N0h0Q3VPNHgxOTI5QjAxMXd1djdnWEhmawpPQm9qa3ZwZnFQUXlRZTk2dGFwRGJqYWZpeStlSHBPSm1lQjFNN2lQKzEzTGJJRjN3alE5SXZ1TWtnN3FQczZXCk15cnBvd1ZwK1BPeDU2SlJRK3lPcm5nakgxRG9FMW45NDBJR0lTZkRmb2g3cTljMkNvSlA2cWo3YWxid1U4RU0KYlB5d3B4WkFTNjYydUtBR0VNcFNLK2NuMXdUU3ZWSDN6NDVrMk9yUmwvQ05PZ0Fad1dyNzdQK1A3bW9FSHlmUQplR0dpclJTWWswUkJtYzdOTGd0Ry9iV0JQTEt4dHIyQmZidDFwZFZXakd4TmlwaDR4c1Z0YldpNnVOeUxYNE1qCllyK0FVUjd1MHlCVWxSc1VUL1dDbkFYdnRmNzRwcWJaNDZ3YjFnajEreU1GWHRNUldVV2NFcU1GVXRJdEsrUngKSlA4bUErbW9qdEdOcGdJZG53b1pPMTBsQkZ2U0ZKL1hGUFlsbHFKOGJpWmJ3RDZtWElzei9WQmdDRHlyQ3kybwpQeVhzR29HNDdTZkovQldvdHUwRkNaZERreCtQU0k2bkdKdyt2empSVzJ3TU9tdzJiZ0xkK3dsVDNpTXp4V3VOCkNidk0wSmpTQ2J3YVMvdE84emtrNGROeVhkWWNQbkJPNVJlM1IrQUV3T0RxV2F4T0ZXYmVUWW10bHlOTXdNT04Kd2lpR3pLWjkwaHM5QSt6M2x0QldNNmxNOFBJaFplcHB1TEZNTDRMSjZ0Ti93anJrOEVVMFBNT2ZlUTVjWXprZAp3QXdiRjVXaVhDd2JtaERCbW4xVVBrMjdPQUV0TzRSM3luaXM0eGNJbmVTQwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
minio-key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS2dJQkFBS0NBZ0VBbmp1N3RwUXd2Ri9Way9NdVJockpZVjBNSnF0ckZKL05YKzFPTEphTWhGWC9LWTEwCkxQK0I2NXcvcGxaR3lKdGJ0VWRXajV3WkxyWlFtSmIzcDRUdHVLNEJUMWd1M2JTWktJSFFOZUFnODFLc01HcSsKNXVYT29QV05yQWhoNGhZT0o0NW1Jc1liYTBEZE8yTUZ2OVd1V1ZuM1Q2Rnp6TTdxTGRDSnpaTmplYUI3bVRKagpkSEdyODRpVDg2QUVBK0h5d3ZzcU9valplK1VqS3U4WHJqeFVHUi9lUEZ2UUN5TWRXZ0ZiandpakovQm44UkNBClJJdWlFc3NqU0xRV09nUWdySUFUdkVGL2N5VWRaS0doYWMzLzRJNzF4RFdoWDcxWFdZd09ORW1yUTZjR3pYbXYKY1VNVjhIdkVXTXpiMDVScE9ZenltS3JieE5MNGVXTmFLdnJ2Y1p6Y016cElNNFJlR0t3Mk45c0FHczNZQlRSNwpXWExLV2duTFlic29IeDNkZFp1eVErSEp3RFRYcXBYdXRZaGFvQ2ZmSDIzbk1NRmlBSzFpbWViQkkxaFVjQWlQCnZwU3g3ZEkzbWdOUDRhZk4vb3FoTU9QZzhUeG13TWVrdnB6LzdjV2JDT05qa2Q5ZHEwVkxMU1crdHFJZmZWUWoKMExPVVB2WHJMT21QbWNMNmxTbFJPODg1VFZ2eEpEYnQyWFVSbWhxSnpwYXJJZk5oRjlFbHBIbWJzZHNsbVlQbwovOUpXVW1ya2JKNkFhZmRsSm5yQ1RHeEpwaXdMMGxKb3hJd3ZxWXQ4REJ1YzFjTkZLSklTTFpJeW82dVhSdU5WCm9OcHJ4Z2ZVdmw4Q2xwM2dTK21JU0ZnMzN1MmtKY2Jxem52dmMzdGJsSGUweGcyTUhNSVZUZFppUmpjQ0F3RUEKQVFLQ0FnQVhHQWE4amdKUzYvWERBeUlFejFJRzZNcW1OaXlKdFEwSGJCNFZ1ZDlHVFRyUmVMaTAvSkdjcnBCSAptWjM1RjF1YUtKQkVvM2ExYjV4eHVNN3FYeWRHNWZhQSt4RFVBTkM5cmJ5U3NHUit2dGtzczllcTRXMTM1bjdICjFlMWJUdmEvNVRPWTdhc0F5MVcrbmlRdnJHTW0zVStRQ3JOWTkvWUx1N3p4Q1FyaXJINTlqSEloZzVtaUVKUHYKWWJKVVVyellva20yZzFTaWxYMjlmV25LWHpteTlRaTliSFQvdXg5RWpLQXRUd2hwQXRoWXdaekc1RTVDU2UyYgpaZFU4b0crWVhaVUR5OWRyR2NhaGNrbVpwSndzelJDbmsyQTdGZXBTd25Nc1JIZy9obmdpc3hqZEFmcUl2N2VYCmNrYS9LWkQxK2xGSjROMzBhd29peFZKYXBZY2VwZk1hMS83dE1vZFFsOXdaOVZLWTZ6YlEwL1U0QndlMGQ0OEYKQ1graVlOZ2t4UWRmdVdwMFU2RkVlUTluR2tPMndZQUJxMCtzSDIxU2puRTQvTXh5anpLZCtjR08zUkdkTktxUwo5QTVubkh4MUwxVDN6Z0hOR2ZHS1F6Tzg5L09sVDBWVE80OEhkamxva0hmc3VTVG03N2tkZkU1TVFwamF2WktaCmo0QXoyWENGWkM2WkJxYm9wZlA1amVNWmI1WDU0aXVtclIwcHpRRGloQ3ZZWmYxTlVDa3hFdFZmaTF1eUtvLzYKMzhQK0pDcEtWSk1mYzhyYTFlWVRTV0ZaZDc1UXVMK1FtblpPVUNqQktXMnNQQTVGbERyTkVTdTQrREhCVVFtOApxdUxDUGdLaHA1TmVJRDVjcm5iVElYclVCb2tQdHpsWm10SEs5TFRYeTNPWkdXUmt5UUtDQVFFQTF0OFRhdWdCCmpMUVI2NXBTbGRXTDdVSnVGVlZUVW9DSlB5cHlOQjkvc1VsTC9Nd1RBbHlhWHoveU15Q2VCdWt3cnBMT1M0NHMKaG5kQlJOL3ZsdkRCaEovVjdYaDBEUWUvMGlqczRJdGNYQ1lpN3hFcWZOd1FQTUJEKzVyWkdKeU1iOEtLV3YwSwpBUnhES0k0YytLUkQwemQ1d1ZtelZSTjdLZlUzT3FXbGV1TjNMTFZqN3R6YU9kT2xSU0E3YWlCTS9odWQ1VFE5CkUwcEF3SDhIaGMxYW1qaUM4dEJsYUZlZ0lodXpJenhNU1hIUkJVcDNsaDMvb2UzNjM4Mm5zRUxjbE4xaFVWRGsKdDNUQVpjdHlYRkIzSEUydHpJdm9xRUpRN0Zkd3MwNUVQZXFIODFOekdjRlRNS1NieVJzNmtYYzhFQ0hPc2lYSAp6TDd5dlI3S1BmVHZhd0tDQVFFQXZJVlZRV3lpcU5ScTdTQkd3czg3WjVjZFlJOGdwSkI4bFlySklqaTRyVUVFCk14MmdVeCtYaHM5QTJSczQxZ1hsYXdvRWNqUDliZXJ2ZTYzMVZOV0M0K3Q5cFR2Vm9qcVhtcnZaNVVEN3V2Q0kKRlFPLy9JSUdqa0tFZkRwSUgvcWxEUlZlbEZTU1JjOVEvY0piZlNwS2JsYnJYZ1FtdG5KOWpsQkpFL1NMSW14UAo3OURVdGlmWmx5cFVRbDl5YzhSZzFSYmpyQWtjQVZhOVBHMXQ3cGhTanJkZHRKbXRVUmtFdGhYWTc3R3c5WHJUCjgwWlJHdkpIS0lsWlBmaHF2WlNGQzg4MVJJZ0lpRitCdWxobm16TUo0dmdYeXEwVCtRY1VGN0FBdFBRU0hyMHIKQm5wN1JlUDF5R201UDd0MjNmRU00Z0R1RENBUHQ0R1lZeUxFY2dpelpRS0NBUUVBaE9MVGJITnR1ZW9IaHpFYQowQ1dRY3p4NVBtSlZ0SmxmeUJ2bEkwMHp1SjMvQzZuZU84Q3ZqQ2JORUVlazA5dFZ5ekZwdWhxRWVPaTZDZkdBCmlGWC9LSmw5UVc4VVBwYkRVQ01WVkUxNzRsV0hsMWlEY1ZMY0MrWlFaUVBBTGROcm14YXlZRkZMNWFIbit1WGgKRHZqd0pXbVN1RHhVaDFJVUFyL3YxeXBvckJhUE5xdzcwSmJ2czRHc0haTXdpNUxNYXY4RGFLUWsvWkFYZWJWVwpIcThBMEk0UWxrREI1b1VDdVBWdWxXVU9QUUhSNWpiR3ZLVnkybCtHbnZEZU8wa3VpRFpkb0YrcUE3ZUY0YTZ2CjNGMjdQRnJpR0xXU1ByVTh2TjNiQ2xsbUpQQ3VBWk5qaE5NbU10Z3FySFpWZzI4OVN6RE5WeW04Wm1qVlVKY0IKTnM0TFh3S0NBUUVBdDRua0tBOFpDZC9NdmxJbk1qREorQit5ZFRqRG9oUWRod1lZcmgybEJ1QitzemxMeHNIQwpKM2lOL1JFNHMzNElEcjh3OXZMUThIdkRicGs5ZWJ0cGRIYm4yNysyVFB4WWIwZ21hc0ZxazJUc1IvRmZyL256CllmczJ1eStPMnJ1T2gzOWZsbkFEL0wxTGI5TVNlWGg4QUpMVkViSmU4ay9qRjNQb3dlbmFyOGZkeDNCOE4xL3kKd3U1dUhEU0szRlM3cFpwa1REQ09PR3QzVDJhR21iMW8yeE9Bd255L3RXM3pIVWVGN2s4RUp1clBnVkRiVTYyLwpRNkw4NUkxL2RsVXJkd1RrS25WNlFUTWl2UWFtei8zUHlVNmE4ekt3ZUVuQThSTGtqVWYyZ0VEUnE3d0JXbGtICkNIaU41NU9ldFpPaVpFSmRnQ2FTeHFrQWNMdi9uN29DMVFLQ0FRRUFxRkNHVDFWWG4yUGEwdFQ2ZCtvRnZYYTkKSENVMTFEbG9ad1hUOTY4cmhGOEJSazdLRVVvZXpFdjZiTUZsdUwzak9jMDNkUUs1WlF0anZUQkZKYlc3NVZMVgphcnR1U0xiVS9CVytnRGtZWmszQ241Z1B6QzlIbGRDa3MrS0lDOHJBcUNPdW9NRzc3SFlOVys3ckJLS3did2w1CmtDQW1uSmE2NWZZczdDWXpEOThmb0crVmxsc25VWCttMUxMZUtjclBEZWlpcW5kQmFTWi9NRVJnWmE2SXZid2kKMDVtNnFqL3ZXL1ZiV05iNVR4Z2N5MWpOOXpRbWJONFJ0Zmdzc3NKRmZzS3JNS0lxVnp1NkNMcEJ4eXBOUXZHYQo0S3UzVFZGcm9zaFlxWUpMVm1xVklYT1dWZk9IQTRMT2VpNmtDZTlHaTQydjdqS014M0dEK25CK1BWbVFXZz09Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==

View File

@@ -0,0 +1,33 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-init-config
namespace: bakery-ia
labels:
app.kubernetes.io/component: database
app.kubernetes.io/part-of: bakery-ia
data:
init.sql: |
-- Create required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- Create monitoring user for SigNoz metrics collection
-- This user will be created only if it doesn't already exist
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_user WHERE usename = 'monitoring') THEN
CREATE USER monitoring WITH PASSWORD 'monitoring_369f9c001f242b07ef9e2826e17169ca';
GRANT pg_monitor TO monitoring;
GRANT SELECT ON pg_stat_database TO monitoring;
RAISE NOTICE 'Created monitoring user for SigNoz metrics collection';
ELSE
-- User already exists, ensure it has the correct password and permissions
ALTER USER monitoring WITH PASSWORD 'monitoring_369f9c001f242b07ef9e2826e17169ca';
GRANT pg_monitor TO monitoring;
GRANT SELECT ON pg_stat_database TO monitoring;
RAISE NOTICE 'Updated monitoring user permissions for SigNoz metrics collection';
END IF;
END $$
;

View File

@@ -0,0 +1,60 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-logging-config
namespace: bakery-ia
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: database-logging
data:
postgresql.conf: |
# PostgreSQL Configuration for Kubernetes
# Generated for security compliance and monitoring
# Network Configuration
listen_addresses = '*'
port = 5432
# Connection Logging
log_connections = on
log_disconnections = on
log_hostname = off
# Query Logging
log_statement = 'all'
log_duration = on
log_min_duration_statement = 1000
# Log Destination
log_destination = 'stderr'
logging_collector = off
# Log Output Format
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
log_timezone = 'UTC'
# Error Logging
log_error_verbosity = default
log_min_messages = warning
log_min_error_statement = error
# Checkpoints
log_checkpoints = on
# Lock Waits
log_lock_waits = on
deadlock_timeout = 1s
# Temporary Files
log_temp_files = 0
# Autovacuum Logging
log_autovacuum_min_duration = 0
# SSL/TLS Configuration
ssl = on
ssl_cert_file = '/tls/server-cert.pem'
ssl_key_file = '/tls/server-key.pem'
ssl_ca_file = '/tls/ca-cert.pem'
ssl_prefer_server_ciphers = on
ssl_min_protocol_version = 'TLSv1.2'

View File

@@ -0,0 +1,126 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{SERVICE_NAME}}-db
namespace: bakery-ia
labels:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
app.kubernetes.io/part-of: bakery-ia
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
template:
metadata:
labels:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
spec:
imagePullSecrets:
- name: dockerhub-creds
containers:
- name: postgres
image: postgres:17-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: bakery-config
key: {{SERVICE_NAME_UPPER}}_DB_NAME
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: database-secrets
key: {{SERVICE_NAME_UPPER}}_DB_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: database-secrets
key: {{SERVICE_NAME_UPPER}}_DB_PASSWORD
- name: POSTGRES_INITDB_ARGS
valueFrom:
configMapKeyRef:
name: bakery-config
key: POSTGRES_INITDB_ARGS
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
- -d
- $(POSTGRES_DB)
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
- -d
- $(POSTGRES_DB)
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 5
failureThreshold: 3
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: {{SERVICE_NAME}}-db-pvc
---
apiVersion: v1
kind: Service
metadata:
name: {{SERVICE_NAME}}-db-service
namespace: bakery-ia
labels:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgres
selector:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{SERVICE_NAME}}-db-pvc
namespace: bakery-ia
labels:
app.kubernetes.io/name: {{SERVICE_NAME}}-db
app.kubernetes.io/component: database
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: Secret
metadata:
name: postgres-tls
namespace: bakery-ia
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: database-tls
type: Opaque
data:
# PostgreSQL TLS certificates (base64 encoded)
# Generated using infrastructure/tls/generate-certificates.sh
# Valid for 3 years from generation date
#
# Certificate details:
# Subject: CN=*.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Database
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
#
# To regenerate:
# 1. Run: infrastructure/tls/generate-certificates.sh
# 2. Run: scripts/create-tls-secrets.sh
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUhjakNDQlZxZ0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGY0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncweU9ERXcKTVRjeE5ESXlNVFJhTUlHSE1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFUk1BOEdBMVVFCkN3d0lSR0YwWVdKaGMyVXhKakFrQmdOVkJBTU1IU291WW1GclpYSjVMV2xoTG5OMll5NWpiSFZ6ZEdWeUxteHYKWTJGc01JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBMWIvVlNmdS9QTXZZb3JiTAoyOTVWMlpBR1JSTld1cEhIM0s5eERBUG00NVR1ZGdQV0x4bnlBOUhWejVqbUtnV0hRS1ZyU0kwNDZ1THVFWUErClJtdGg3RkVWQ0x0OWk1aWZoYVhtQWZTb3VHOTFuQzJOQ3NobUVoWHRaQkpYMG9tYU5oaUREb3R4NzhrakthTFIKQTIybVFvQ2NQdmt6RXFPRUNwaVZGVTlVSEIzQzV1bm10SFNDNDhiQitBUnlpRTJ6N1JyYUcxWUVLa2lsamlsRgptSlRTNk4zNkJxYWJGNkF4cVNwSWFub0VnRmdXQzZhSVh0QStmbzNFejFtSkVGd2Z6UUJXY0t0L09OM254M3hECmJSTnNtb3J4SHBzUGluT0E0aEhWdzdUY1U0THFxVVJZZGROb2NtYmtLaVZYSlpFRmdMZW5nQjFsbS9sQVlXcVoKUWRQYlQxVWNDZlFMdlN0NmxWaytWQjA2ZVo0WktmaS9rb2ZsRlAwZisyU0IyaFE2YWo5N0cvUmJya0NHYUlGWApDeDVkNjlBb3FTd3VFeHRYL1FtMVVLME8yeHBMdjM1S2RTY3krWjFJRk9jWXpjWHEyOGZ4bXUrVERETnlTU2NLCmxzYmp3ZnU0RUdLR0xza3RHdlRBR0gxRXlLdktrc3F4MEV4OXMvOHZBaS8yVDQrRkMxQmwyNUI1ZnpERUQ1RHAKS0h0SmF0eHdqV2lpRGxheXJrOFdnMDNSeUZTZjVuNEY3UmJwMytvRm1zU1NuRUVaK1JDT25DZ3FDWlkxTXM5cgpGVDlmejdoQXMyK1hQZXB1MHZ3RktCVXdseGlXZER6SDZzRElRQ2VTM3hTMjQzdnlpYXRFdTZLOEM3eDBlV2xzCjU5SUJRcXY1eDJUYkZ0VHdEWGdiK1NKMGsyVUNBd0VBQWFPQ0FlVXdnZ0hoTUFzR0ExVWREd1FFQXdJRU1EQWQKQmdOVkhTVUVGakFVQmdnckJnRUZCUWNEQVFZSUt3WUJCUVVIQXdJd2dnRnhCZ05WSFJFRWdnRm9NSUlCWklJZApLaTVpWVd0bGNua3RhV0V1YzNaakxtTnNkWE4wWlhJdWJHOWpZV3lDQ3lvdVltRnJaWEo1TFdsaGdnOWhkWFJvCkxXUmlMWE5sY25acFkyV0NFWFJsYm1GdWRDMWtZaTF6WlhKMmFXTmxnaE4wY21GcGJtbHVaeTFrWWkxelpYSjIKYVdObGdoWm1iM0psWTJGemRHbHVaeTFrWWkxelpYSjJhV05sZ2hCellXeGxjeTFrWWkxelpYSjJhV05sZ2hObAplSFJsY201aGJDMWtZaTF6WlhKMmFXTmxnaGR1YjNScFptbGpZWFJwYjI0dFpHSXRjMlZ5ZG1salpZSVVhVzUyClpXNTBiM0o1TFdSaUxYTmxjblpwWTJXQ0VuSmxZMmx3WlhNdFpHSXRjMlZ5ZG1salpZSVVjM1Z3Y0d4cFpYSnoKTFdSaUxYTmxjblpwWTJXQ0RuQnZjeTFrWWkxelpYSjJhV05sZ2hGdmNtUmxjbk10WkdJdGMyVnlkbWxqWllJVgpjSEp2WkhWamRHbHZiaTFrWWkxelpYSjJhV05sZ2hwaGJHVnlkQzF3Y205alpYTnpiM0l0WkdJdGMyVnlkbWxqClpZSUpiRzlqWVd4b2IzTjBod1IvQUFBQk1CMEdBMVVkRGdRV0JCUitaeU1BTUNNeUN2NTBNSlRjSFN3MTNWVjkKM1RBZkJnTlZIU01FR0RBV2dCUUQ3cXIrUnp4OU5CVFVRT3JNWkY4cENtcnFiVEFOQmdrcWhraUc5dzBCQVFzRgpBQU9DQWdFQUM3V0NOM2FKdzR2VDNOcjVmV3Fqa3p4Y2wrc3BUUnlCREViSlpZcDNIZEszUU9peGhUZDBCM2JGCkZ6V1NJWDc5R3Z2cy9yajhTWkRYUDNCZHNTcG9JeFRKZitpbnpQbThoUFJvMmN1U05qTzl5aGYxdTFBQnliMmcKZVdtMkw1OGRMTElZbmdjc2wvQWFUaGlmT3VLZlZjN2tYNUY1K3BwSGxXRTRJTkdhT0tsMlpxQkxwT20rNG5YcAo3OGlCQXRmSEtWTG1NQmtJRHNZZ1g5RURVNGdZWWVyU0V1WTNUYWM5NGVhOW5FY0dwdkhEaEdSYk5SUzQ2RmwvCk8zVmoxOE9TK0tkZE1lckF1ZU5qdm9wNXZzSzBUNk1DZWxMT2hscnRvTWVOSEVjd3prQkx3anZEbzJHV2FIbU8KU3lKTndTRUFqbHlVMXJyYTBUWHRweU1nNi9jbldicjlnS2hybmYrTzBDTUdMdVYwOEZpUEN3YTYvcW1QYWlLQQppMCs2VGJ1c2JGTEdrZVdDUEszanlaRmFsU1puV1BINWRMSkV3dVNZZTlTRnhaVlpTSFNNV24weXR2NUh1Wk5qClpJbnh2YmpqNlMrYTVTZVJjNXB2ako1Vk1Ea2tRSjM0bUJsMjJub3lCaXA4Y3J1UWdBN3l6SG45c3ljYkF5VGsKWWdOWEpIbmI0UW11dHJiTTd6YnFrR1BORlhFQnl5VmFZL203WnJsRTNvRzRHUmxOc3NtS3lVZ3ZMUHhVbWdZSwpwNFg1eERoUlFsNE1WNDkvL0E1RjYrcVM2dXIrQitwOXhIb0xKZm9CUlRTVytTNlB1YmI0d1FINDl6cDNObW05Cjk0YVRoaktISzhUaU1iSkErYlEva0YyT25KWXVua3VjWWpZek52ald3ZjFTL3JlQmcyRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server-key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRFZ2OVZKKzc4OHk5aWkKdHN2YjNsWFprQVpGRTFhNmtjZmNyM0VNQStiamxPNTJBOVl2R2ZJRDBkWFBtT1lxQllkQXBXdElqVGpxNHU0UgpnRDVHYTJIc1VSVUl1MzJMbUorRnBlWUI5S2k0YjNXY0xZMEt5R1lTRmUxa0VsZlNpWm8yR0lNT2kzSHZ5U01wCm90RURiYVpDZ0p3KytUTVNvNFFLbUpVVlQxUWNIY0xtNmVhMGRJTGp4c0g0QkhLSVRiUHRHdG9iVmdRcVNLV08KS1VXWWxOTG8zZm9HcHBzWG9ER3BLa2hxZWdTQVdCWUxwb2hlMEQ1K2pjVFBXWWtRWEIvTkFGWndxMzg0M2VmSApmRU50RTJ5YWl2RWVtdytLYzREaUVkWER0TnhUZ3VxcFJGaDEwMmh5WnVRcUpWY2xrUVdBdDZlQUhXV2IrVUJoCmFwbEIwOXRQVlJ3SjlBdTlLM3FWV1Q1VUhUcDVuaGtwK0wrU2grVVUvUi83WklIYUZEcHFQM3NiOUZ1dVFJWm8KZ1ZjTEhsM3IwQ2lwTEM0VEcxZjlDYlZRclE3YkdrdS9ma3AxSnpMNW5VZ1U1eGpOeGVyYngvR2E3NU1NTTNKSgpKd3FXeHVQQis3Z1FZb1l1eVMwYTlNQVlmVVRJcThxU3lySFFUSDJ6L3k4Q0wvWlBqNFVMVUdYYmtIbC9NTVFQCmtPa29lMGxxM0hDTmFLSU9Wckt1VHhhRFRkSElWSi9tZmdYdEZ1bmY2Z1dheEpLY1FSbjVFSTZjS0NvSmxqVXkKejJzVlAxL1B1RUN6YjVjOTZtN1MvQVVvRlRDWEdKWjBQTWZxd01oQUo1TGZGTGJqZS9LSnEwUzdvcndMdkhSNQphV3puMGdGQ3EvbkhaTnNXMVBBTmVCdjVJblNUWlFJREFRQUJBb0lDQUFYcG5nZnVDa2xhRVg2Q3Z2Q0YzY0JuCkR2MVhLUnJWRUg4UmJVenZTTEk2a0Q2OGRzUVdjWG1GNkR1clY1RTBWa2J3QWNxS2VZSVVlcEJmczBQMXZCK0gKZmZwS3NXWXNvbkVBZUo4aU9qazJnQkxYWWJqa0lvcXFoNXdHaVRPemh3d0FXK2tKbGhlK0ZtdSs2MkxadVhQSwplZktncUdIWGNHakRTcnNYQVgvR1JQb1NpMFZVN3cveVBnaFRHeXRHWWFLVDVSSkUxcTJvRlIyY2FsRkJBSi9jCnVyU2lEdFUxb3dTeVo0Njd4dnh1aUt3KzFEUGNpbllCWVpSNHZoQUdud0huMmZ4RGlpdmVXNGNnUlZTSVBvU24KTU9udlVSdm1lN0N2M0p3TmJkdHpoTWswMjV1UUdGU3pJRDd0aWRPL1hMUndTc0VDVHlsa0xpYzVCYzgvMXllZwpKcmxyRU1hNW8va3hvRGtSWjNQMHhZd29mS3gxMWVlRXA3SmNoaHRmajRzYUNWeEw3aHlmTzVQRTFSWTV2UHVmCjlqcEVGUTNJbDBsMjRRUTU4TWthN0gzZCtSdzNjbk1MYkpTSEE3MUdSOWFqZS9WUVVPcmF5MG1XZnRkVjYrVGEKWlAvdDBrL2pqcWxxUmlxek9ZMDhrMGY4dGptamhzdHgxQ1laaVJQQVhydWlFb1N3UzRGQVV2VHdSeW8wODdJMgprZ3NYbTlGd2NFWkRVbXpsMGxURy8xYk5sSEVsdWx5cVJMd1plUXdMcEF0ZTNFdmpNQzE3TnVCbkZUOFd4bHRjCjhzVGRUczNNSEZNdnZiM3IvaXJjelkwTncvdzd3czhOWkZ1VWxXMm4xTE9iWkNrTUNIaVc2RldPRUFhSmNzbXkKMXlQbEFaMXB0cGJ3b3IxdzAvMTdBb0lCQVFEOEFJMlpvNktMOTRRMGpPYmltcm5yQlRXUW5aSFp4U240L1ZQTQpTQ2hOZ245bHR0QlJxMkYyYXZlU0xHMlRhRjRCUWRjR0tSQTV1ODZPdnp3N2VFWUpkdEsvQnBQRjZFa0hERlR2Ci9EVXBTaGEvZ2JCa3FtUmFESmZGamc5cE1QWmR2Z0VWeHlMWW1zUlliLy9FOFI3dUxlbDA0aXlwQ1UwSVNsMmMKZlVOTGZXa0NBNGk0Y21kSE1xdEd0bm9LbnNXcVYzVWsybUVRSkpZSTY2UERtcjNLVndvUk1vcVpNUDRwcjE3NQpSSG5rQTZmOWxFVzh0a1VYbnV0Vmk0MW5zOEpoRlpmblFaREtmWGR1VDQxN0dDSGVHa2tXblhpenQ1ejZNdVdtCmhMbFErUDY5UzZpVlNRUU5uS3JaWnVFdUZOVE1ublRTZ1ZPdWZuUkxWWDFjZDRFTEFvSUJBUURaSSt6aWFxenAKRktqdmxaUnlCa3A3aUYrSmpkUDc5QmRmZUswU0pWT3QrRzROVU5RT1BMVXhjdUIxeWVYd3Z2NVhWaWtkOHR4SgpGbVZMcUNNNjlhODFSbkhHNnVOTlNVMW1vRTFWQVgwd2p3ajFoaStuQUdjZWdJcGRpdHpKckJpUDhsZGtWNStyClpIaUM1L1F2SDcrUVphQXVRNnMwZmdoUEk3cXNmWFNucU5TNVcxNEFzYWJNcVBZUHhHcjRQMEJPaEVjZ2R4dFIKRjY1SFB6OXY5clFkOUxtT2JJWTMxOENrTTdtY2ZzRys2Y2tBd3RRVWdGdmVmZ3RTOG4vMGR0Rm1Ca0RUZkF4cApBU2ZENWk2Nkw1Y3g2Qm5VTzFnc2dNUHBMamtzaDVEMXFaK2d5Tldrd2xRbERSTHM2SXVCRVc0dkVuSWMxYVNsCi9BUE95MnBNMWVOUEFvSUJBQkVIeElvR2lmeWxqSlMwbFFIcGJQa2FFQVdtOEcxa0tyTCtBOFRCZDUvTld1aTMKMHhwQjE4TlY5VWMybzIwYjE0YUVPWkRjQTVHelJJRlhJUzN2c2VQLzJMdzZLSkJ1WTBrTHAwM1VvSThheDdESApoZkUzcHJLRE9WcUxnRFVlcnZlazJKUHRNa2lySk92SkhlTGtYSy9DQUkzNm53UUpjZUJHams3K0ZDY3M0WVRXClVrNE14VGdGajVlbXkxYWVaa05keDdmbTNqcG1EcEdwd3haOEJhbC8rbGt4TGphdUhlOFpQL1Rla05JOUFRUmQKR2Qxb0FBRlpweFBQNjQxL2szcFdLRDdqcW5KVXlsWjFIOTJhd3Vjc3BaWFdySXFRdFJZZmpHK1ZkcVNuUHlmeAp6Z0hRdm1waEZSYStJaWVvRnIyQlUrbkovYXJFTnYzRVdFV0FlZ01DZ2dFQVQxVVl6d0E2ZkUzWUN2Q1RjN1ZvCnNRbDZIajk3RzZwcWY2OFBUSG5td01Eck5HSTdsNWdHZXpLRlg0T01SeEVBeTlmbTNkSkZPVTY5WTQ3aWtFQUMKNjJ2NVZidXJvQ2tQNWxiYTZodkpLVnlZNFZ0TlBhNmYvanpvVUpUVFpidENuaFRrYVB5NmtWdjd5NWdEVnRRNgpvUDhBTHViNlBndHQ3YndZRDcwbVNic2RQVHRzZE1Sek5JTG1vNHdYcU9zekMzeTRuOXZrVnhSWDBDQURoVnlWCklmeXZicUdueCs5RHFycGJMaG9CbjBhNjhWUTlOK0JOc0ZSTXZ0bHFkbDZTMHJ1bUk1NUd5blpwbU9FWVlWM1IKMTZIOURkVkF1Y0d4MGhmWk83T3IrcFVtaFEvYlBuN2hUMGdmaWY3TU9UT3RGZldmUzNtaTFpSGxJa0NmYmNNWApjUUtDQVFCY25sMFRDVU1JZFhiTW5JRzEwQ29MOW15SFdsMUpqSFYrTDdJOVRRL09rdnV4dUlvSlBSYnBQVURLCmRuQkNXM05ZODZ6c2dMKytJNWIyTFdoWHpTamxBZ1pTRDVySklCenY1Lzh5ekdoNUVaSzUxOXdnL2JDYkpMREUKTFFsUTcrai9CS1VsbG1ySUtENHZva2lyOXJvbkdKblROSjlhU09kdEQ1ZDd1M1ZCZkpkRGltS1J0M3VVcHdabQpCbkxyTFlaS21SVW5aK0k3SGdyd0NPNSs4MTVXNlh1dDlQaGR6NnBwOXJUK2Z5b1VoeTFWK3VpdTJhVDFQbHJTCkhTdUFvdFdBa0lZS2I1ZWlQd1NBeXRvbWdmYnA3R2JBRTRtY1A2d0l1eFhMbkJneVpIbzBhM3FCY3drRnlXYjYKMStBR3cyMFcyaHZnY3dKNDRjTEgySUUyOGR5NAotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==

View File

@@ -0,0 +1,179 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: bakery-ia
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
app.kubernetes.io/part-of: bakery-ia
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
template:
metadata:
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
imagePullSecrets:
- name: dockerhub-creds
securityContext:
fsGroup: 999 # redis group
initContainers:
- name: fix-tls-permissions
image: busybox:1.36
securityContext:
runAsUser: 0
command: ['sh', '-c']
args:
- |
cp /tls-source/* /tls/
chmod 600 /tls/redis-key.pem
chmod 644 /tls/redis-cert.pem /tls/ca-cert.pem
chown 999:999 /tls/*
ls -la /tls/
volumeMounts:
- name: tls-certs-source
mountPath: /tls-source
readOnly: true
- name: tls-certs-writable
mountPath: /tls
containers:
- name: redis
image: redis:7.4-alpine
ports:
- containerPort: 6379
name: redis-tls
- containerPort: 6380
name: redis-plain
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-secrets
key: REDIS_PASSWORD
command:
- redis-server
- --appendonly
- "yes"
- --requirepass
- $(REDIS_PASSWORD)
- --maxmemory
- "512mb"
- --databases
- "16"
# TLS port for external/secure connections
- --tls-port
- "6379"
# Plain TCP port for internal cluster services (Mailu)
- --port
- "6380"
- --tls-cert-file
- /tls/redis-cert.pem
- --tls-key-file
- /tls/redis-key.pem
- --tls-ca-cert-file
- /tls/ca-cert.pem
- --tls-auth-clients
- "no"
volumeMounts:
- name: redis-data
mountPath: /data
- name: tls-certs-writable
mountPath: /tls
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
exec:
command:
- redis-cli
- --tls
- --cert
- /tls/redis-cert.pem
- --key
- /tls/redis-key.pem
- --cacert
- /tls/ca-cert.pem
- -a
- $(REDIS_PASSWORD)
- ping
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
failureThreshold: 3
readinessProbe:
exec:
command:
- redis-cli
- --tls
- --cert
- /tls/redis-cert.pem
- --key
- /tls/redis-key.pem
- --cacert
- /tls/ca-cert.pem
- -a
- $(REDIS_PASSWORD)
- ping
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 5
failureThreshold: 3
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-pvc
- name: tls-certs-source
secret:
secretName: redis-tls-secret
- name: tls-certs-writable
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: bakery-ia
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
protocol: TCP
name: redis-tls
- port: 6380
targetPort: 6380
protocol: TCP
name: redis-plain
selector:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc
namespace: bakery-ia
labels:
app.kubernetes.io/name: redis
app.kubernetes.io/component: cache
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: Secret
metadata:
name: redis-tls-secret
namespace: bakery-ia
labels:
app.kubernetes.io/name: bakery-ia
app.kubernetes.io/component: redis-tls
type: Opaque
data:
# Redis TLS certificates (base64 encoded)
# Generated using infrastructure/tls/generate-certificates.sh
# Valid for 3 years from generation date
#
# Certificate details:
# Subject: CN=redis-service.bakery-ia.svc.cluster.local, O=BakeryIA, OU=Cache
# Issuer: CN=BakeryIA-CA, O=BakeryIA, OU=Security
#
# To regenerate:
# 1. Run: infrastructure/tls/generate-certificates.sh
# 2. Run: scripts/create-tls-secrets.sh
ca-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZ5ekNDQTdPZ0F3SUJBZ0lVUGdPcU5ZK1pvS0J5UTFNZk84bGtpR2hPbXhJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncwek5URXcKTVRZeE5ESXlNVFJhTUhVeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVdwpFd1lEVlFRSERBeFRZVzVHY21GdVkybHpZMjh4RVRBUEJnTlZCQW9NQ0VKaGEyVnllVWxCTVJFd0R3WURWUVFMCkRBaFRaV04xY21sMGVURVVNQklHQTFVRUF3d0xRbUZyWlhKNVNVRXRRMEV3Z2dJaU1BMEdDU3FHU0liM0RRRUIKQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURSRDVPMmVna1lnOUhOUlI1U1UwYkxuR0hqcHYvUmFnck03ZGh1c2FXbgpyZkRGNVZwVFo0czkvOXNPRUowTnlqdW9LWGFtb3VUd1IxbncxOUZkSDhmMWVvbWNRNGVLdzJIa3hveHFSMzR0ClJEYUFHejNiV08rcmFUUTRTeU1LN1hGTW92VVVpTGwrR08yM2wxQk5QZmh6a2NEa1o5N200MzRmMVFWbzk5dGIKaFY0YklMYW9GSXFmMDlNMEUxL2ZhQitKQ1I4WWtsN0xvWGd1ejNWUi9CVW5kMHZNc1RNV3VlRC8yblZ1VVpPMAowcFVtVFVCUTJRZDc2NTdrL0hXZC8xd2NFQUw5ZFhOUmJ4aEROZkdnYzNXdFFoZ2djcFlMUWFmTGE4MXRseHljCndEZ042UGRFbFVseGdYL091b1oxeWxNWkU3eHBzTXRwbjFBd2VvZFZibTNRcDVBMXlkeWJFNjF1MXVyWXoxTHQKV05aOWVPZkFxZXdpWVFIVlpXTUM0YTRTYSsyeU02cTVQWC80ZytUYklUaDhoWkp3WFBLNUVEaWc3dkYxNEpQbApsRVJOcHdpYTNuNmEwUDcwM0hQTjZya1FPNWtWVGRpVXNmaWJNdGNVSkhMeVdXUUFSQm15ZVZma0lDYWFlWUVsCkVMa3N3YTlOVkVTS3ZRYUhLU2lIWkZoRUkwYUF2Y3BBam0xRU9oRWEraFNSaE9vRnlVT3ZHK2NNT2ZjQlNtTDAKVW1sRC9sZmFuVFQwems1YXFzcEVrWEdlQnczMXJtWi8wQVpPalYycHBSeFdXZWt6bzlCZjdnNmVMVFk0VUNDNQpNeVB0em14OVRiWHJOQW5YaGlGNkxnNWgyOFI0MkdUZTVBZDZUSGtGOVMvS2hxOHUwZFk1U0EyR1VGMUViUU84Ckt3SURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3SHdZRFZSMGoKQkJnd0ZvQVVBKzZxL2tjOGZUUVUxRURxekdSZktRcHE2bTB3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcQpoa2lHOXcwQkFRc0ZBQU9DQWdFQVF1dkZoMitIUUZ5OFZUY1VnYWxFVmlheXQxelFHdjRySVNtaXEzRzZJZVhQClhTNGd3cUhrRnpUd1p2bW9oVHdtT0N3Vy94RjRLZ3htRmJ5V05yRUpKRXFjYmVkcVVXVi8wQkNhRm1KdlVkZEkKK2V4L2lEM0ZlYnU4QUZJK0o4bEJIL0NlbkRpU0xIaGd5c2VZOHV3Um5Yc3NoWDVSbkRpckYxdUtyMUo2MzVhbgpHbHlGSU5Vcm5RbGd1RXZ0cjBlbkdVbHpUNXJXajR5MEFXVWRiWGk4dlJzaldvUThKYTBCeFRyWVloL2tPL0ZJClB0cVg3d3N4b0pNREVRNzF6aHdhN1dMUWMyZGZiMnJBcjF1QmgzcU53aVZCSU5CK3QzSkZ2NzJ4cXNXZ3VySUIKSWYyc29SVEkybk1lNWdURzFEZmQrVjI0amZhL3lJZ0FzTWpDem1HUUsyMHZvYlg0c0FWbm1QVmJaZzlTTEZaaQpNaWRrbjlPOVU2OE1FT2UzSWFzY2xkN2ZwNUprK0hyYkpVNi9zMTZFRVIvQWdEM09vajN3UmdqVENTK0FERCtqCnhvMk84Vlgya1BvMDNBTitpWWEzbkptbE1GekNyelQrOFp4U25QNUZxR2cyRUNFYnFxQTBCLzVuYVZwbWRZYVYKNDFvRkxzd2NGbTJpcUdhd2JzTE45eDN0dklDdUU5M0hZazFqNzJQelhhaVNMdHB2YW1IMWRSWUMrSFVNMUwwTwo0OUNOTVlKZUwvTmx5UXVaSm0yWDBxRE5TWG1STUw4SFU5c093V1g2cFBQSk96dXF0Z2R4Lytsa0dBZDJ3WkpVCklWYm1MNlF2emRidGEvY1NWd3NMdEJ6RzQ4YTFiNEtCYzdXTEhUd2JyZEJSVGcwVGtMWTRrdkNaZTVuTmw0RT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
redis-cert.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdjekNDQkZ1Z0F3SUJBZ0lVRytCME0ycnhucWpHZHRmbzBCaGV2S0N4MGY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2RURUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZUQVRCZ05WQkFjTQpERk5oYmtaeVlXNWphWE5qYnpFUk1BOEdBMVVFQ2d3SVFtRnJaWEo1U1VFeEVUQVBCZ05WQkFzTUNGTmxZM1Z5CmFYUjVNUlF3RWdZRFZRUUREQXRDWVd0bGNubEpRUzFEUVRBZUZ3MHlOVEV3TVRneE5ESXlNVFJhRncweU9ERXcKTVRjeE5ESXlNVFJhTUlHUU1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVgpNQk1HQTFVRUJ3d01VMkZ1Um5KaGJtTnBjMk52TVJFd0R3WURWUVFLREFoQ1lXdGxjbmxKUVRFT01Bd0dBMVVFCkN3d0ZRMkZqYUdVeE1qQXdCZ05WQkFNTUtYSmxaR2x6TFhObGNuWnBZMlV1WW1GclpYSjVMV2xoTG5OMll5NWoKYkhWemRHVnlMbXh2WTJGc01JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBdnNhMgo1MUdFR0VuaW81NHUxdGtNeFNCTGQ4Mm9ML3VsYWIxYXdxREJqcUJkVUFpZzJsMWpScFFyNUxHNVh3UzVoNzM5ClkrdnlQbFpWZW16dVRiNmszbjhPckxNcnUvUFBSRytpUWc3cXlUR1orYmF3YWY2YVhFZUNLOEExWW5xSy9ONEsKQTFIUkxXRXNXRzBKQ2VZakZPZnFzempWTEtydFJhSDd6S2lBREZxREJCbXhScWsvUDJvSjZmK1hXaWpwNE5KdQpPaVdoQmNoYmpjRi9mTTZ2MGVsQlMvOGs1cHVpOGtFdWRNZWExSVFLNXFTSll3TjZZNXRNT3BKcm1IdTFFN05vCkJRZWduakJvMWJaUkFkMWgrL2NxOHAwWWt3amE5dTJnSk5jczMxcWY4MUFtNitxNklXMExqTHFUMnlINVU1aW8KS2hTa0FuczNwcUFPNFZrSWRuM3l0Y2tkK00wQmNNcTRKQm5iYk0vZ1ZPV3I1RXorSERKOWsyckFSbzBWWFB5cQpnT1JxMnNXU2N0eVFiV0pPdExOUWVVbUN0dXZ4d0RyVVNoQWlYZGhhM3ptejdYOWJiNCtWUXA2elJaM3h2bXBnCnFFeG1Pc05zMDBMak9sMHBsalVmR0ZBR2Rmb21JSXpSWmxnVkN6TVVsWkQ0cGNQVnNhSGJuR1ovNi9ZbXhNZGUKOUxjbjRrYmlrNjVmZEFJbnhmVFAySU1NZER3TUZkYkZpcy9SbDIwZWo3QUJ0YTNLdVhvZFluMXkwbitYTFIyTAo3YWJUcW9xSXRnUW1BY2lITlBVYWNnREMvbFBRSk95ckRaVTloQ3NMdDJJVVZKTUN6U2QzR3JDQzA4d2dSb2U1CjZRNUh0NEUyWG5kV3NlWWZxVnRRM2c4WktDaVUrUU1JQmt4SzdHOENBd0VBQWFPQjNqQ0IyekFMQmdOVkhROEUKQkFNQ0JEQXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUcwR0ExVWRFUVJtTUdTQwpLWEpsWkdsekxYTmxjblpwWTJVdVltRnJaWEo1TFdsaExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc2doZHlaV1JwCmN5MXpaWEoyYVdObExtSmhhMlZ5ZVMxcFlZSU5jbVZrYVhNdGMyVnlkbWxqWllJSmJHOWpZV3hvYjNOMGh3Ui8KQUFBQk1CMEdBMVVkRGdRV0JCU2RJV1V6Q2gvNE9SZmJLR2JYTVJ2eXhXTFdyekFmQmdOVkhTTUVHREFXZ0JRRAo3cXIrUnp4OU5CVFVRT3JNWkY4cENtcnFiVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBaEd2cFBSSlpqQkZpCnBaNDNVaFVGTGFIeCtRMHZncy96eXlxVzVqSys3ZWZwY3Z0Sk9CbXVrRUtMaXUwWGFrZit5VDhWRlp4R2tzZkYKcVZyL1Vvb2x3bTVEamlHOE9FT29PYTJCZTlqb0dTdmI3c0JzZ24wYS9pdElGUnpEUXdadGJQZmdpMGFndkJmTQpxczFCNm9IempBMkdSNmlMUDFXYzg4VXRNWFJwV1c0VnZSclZIallzWHVuM2ZHdGYxR1J3ZndBWFFJNys5YldpClNPQ2hEOWVJNk1xdUNYQmRCQVgvQ3FueGs4aHVia3dXY3NIeVNGQkRMcVFoUHM1dU04bGkzK01QZ3BMWENmYVkKWDYvWnpIM05nSjNEK1BJSDU5WllwaEczenZsRnBHRDRvRzNRMkFvbHhxd01SMytQNmM5SWYxRGZNTW9TZ1YzKwptZnZnUmpONXRuZ0IrL29CaXVtYk00K0VGOGFNUmsxR095V3BmM2VSZkc1NStPVEpsTHNEWE9TQlcrSzFOQ3o0CnlOWVR5c2h3eGpWU1BYcWZhdGVBWnpDNVNqRk1SZHJkSEQxME0wZ2w1L2RYY3AreDVocFFTWTNNK2dNMndXZEkKem83SkJPdDlRMUZRRGdUM2pJVldRNVB0TmhkOW9UOVdkYzBFZEppenlObDN2aTltMi9iSktWcEhPMnltZG5IWQpoUG12UVlWdGZzTWxOdmtzdkRMcFhlbUc3czR2akhmMTJZRVA4VFQ1REpnRDQ2TlZvZTM5S2E0N0lmYVRXdWdOCkZXb1YvUGFqUkl4L0lPL2tPcDROQnBlQjY5TytudVlVVU5jQ3cwZmlsQSttRmdXUWpxRkdQK2ZxV05hSmorcFAKNTByelBOc3hwK3FpdzZGVm9aNTVjY3hFMjdrbnZlWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
redis-key.pem: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRQyt4cmJuVVlRWVNlS2oKbmk3VzJRekZJRXQzemFndis2VnB2VnJDb01HT29GMVFDS0RhWFdOR2xDdmtzYmxmQkxtSHZmMWo2L0krVmxWNgpiTzVOdnFUZWZ3NnNzeXU3ODg5RWI2SkNEdXJKTVpuNXRyQnAvcHBjUjRJcndEVmllb3I4M2dvRFVkRXRZU3hZCmJRa0o1aU1VNStxek9OVXNxdTFGb2Z2TXFJQU1Xb01FR2JGR3FUOC9hZ25wLzVkYUtPbmcwbTQ2SmFFRnlGdU4Kd1g5OHpxL1I2VUZML3lUbW02THlRUzUweDVyVWhBcm1wSWxqQTNwam0wdzZrbXVZZTdVVHMyZ0ZCNkNlTUdqVgp0bEVCM1dINzl5cnluUmlUQ05yMjdhQWsxeXpmV3AvelVDYnI2cm9oYlF1TXVwUGJJZmxUbUtncUZLUUNlemVtCm9BN2hXUWgyZmZLMXlSMzR6UUZ3eXJna0dkdHN6K0JVNWF2a1RQNGNNbjJUYXNCR2pSVmMvS3FBNUdyYXhaSnkKM0pCdFlrNjBzMUI1U1lLMjYvSEFPdFJLRUNKZDJGcmZPYlB0ZjF0dmo1VkNuck5GbmZHK2FtQ29UR1k2dzJ6VApRdU02WFNtV05SOFlVQVoxK2lZZ2pORm1XQlVMTXhTVmtQaWx3OVd4b2R1Y1puL3I5aWJFeDE3MHR5ZmlSdUtUCnJsOTBBaWZGOU0vWWd3eDBQQXdWMXNXS3o5R1hiUjZQc0FHMXJjcTVlaDFpZlhMU2Y1Y3RIWXZ0cHRPcWlvaTIKQkNZQnlJYzA5UnB5QU1MK1U5QWs3S3NObFQyRUt3dTNZaFJVa3dMTkozY2FzSUxUekNCR2g3bnBEa2UzZ1RaZQpkMWF4NWgrcFcxRGVEeGtvS0pUNUF3Z0dURXJzYndJREFRQUJBb0lDQUFGdjRtMTlwTFFXSW1TVWRYVXkyZ1liCmNkWVdNTlVqc25iekc5MlVIbXZNODNHb2p2cjJISFdwK2hGVlJyaUdMWlpETFJ4MVBqUTZyRUYrMCtZTUJldm8KZUhEVDdLNit3eFNZanExV3RXMWg0cG9KOFVHVnp3M2JrQW5LVklkSVlGeFA3b2dMTkJDQkhJeThvdHZMT3YvQQorM2ljSTFHY2ZBQm1uRXlmWEUrUTJFOGpRNzJYaFhMSExBbnlNMFAvbU9ZVHBRdy92NlhEMWtTMndoZHJsZEYyCm8xZWM0Qkh6VEMxQ1VScEV3cVY2ZjlFd1NNU21nR1BZVzB1a1VndlZBQTZFN3h5bjY3Z2xWSW9xUHhQM2hKeHUKOFRPTFVXVzh6d0Z3Z0NDbTZrbnpGeVN3WkRWVXV2cmVKUlIxOTFVb1BWdU8yU2dhcUYyZHdLazYvV3hmSWxHQgpoRndkbmN1Q1UwdVV5QXp3VUh2bGlEWndWUFFxaVBMbXFYWEp3WjY5RjUzMEZlVHM4L2hUU0Y1UTAwaUFqTmhlClhRbzhJQjA0U1N2VDdMQno1OVg4Y3M0Mkh5VG80YWZ6bWhLK051OEsvQ0ZxOERMT1orRTFtYnhYRE9DM1ZWVHAKaDFFaXd1a0Z0ekpxRzVRSEJjTTlNNVlTK3EzaUw4YXY2N052M29wTm0vUG5YWkdYenFtVjRzK1FwMDdtSUhiVQpsamFCcWVzNGN4RTZZRUtkS1NOSnJ6Y09EVFNFT2hOYUJXN2RNSFRmay8zbXBpODIyNENBdEVJcmVlZy9Ua2VBCjJLWVBmTzJEd3hYZHZJd1NvajBSM0JDbkdVOWVRKzl2L2c5WVU3SXRyS2UxQjlFZTAxNjNUOC9tbnFlZy9QenEKOFNDSFA3Yk1Zb1gxaUlmbjk3MXhBb0lCQVFEZWE2YlY5blQ1dVJHL21FK2FLd0pFTHdkTzFCQTdwc2RIcnV4UApjSW5Hcjdqa3g1S21KV3gvU3c4RXdRZjR1dThEcjYxcC9QUDZLSTZoSzVtQlJhOUpWeVdVbUhTaFFDb0g5TGhPCk5mMkxtMEVOalZVZkdOb2JHMzhsbmhLd082QnNKS3JxTzc2SW5rc3hrN0htaGZ6emlBbFVtTDF5dFhFb0s2Qm4KM3BHZHNRZzEzYjlnWCt6NXZVcGlEOHI5R0U1Rm56cDhNa1BsTWhqcWsvVmp3VXNKcGluSDhMY1B3aEMyZlM5Zwpac2dYdmt6MVR5R2FZVHU5LytBazBMZzJqMU5kNFY0SmIyR0Fvc1NDRUtGQnJrZVNVMTVLK2YrOEtIdFFtMVVBCjBqaExWQWpUTkx1U3d4elB1VUpEaGF4K3kvRFpRRmJPRG1kQmtRWXFBWFpDL0pKNUFvSUJBUURibEFwTGg3c1QKcjhtbjdFcUxEU0ZyVDlQSitsQnhqL210bWd0QVE0MjhBNXVhMFVzbGJ4NGNid0pzcktlejVldkhjWGdmL1Y4cwpBaTFtNnJLcmFBOWlMaFFXSk1wRkFoOEZvRnlIK0pFN1l6N0F3elY2WXRha1h0ZVlrNVIzSlg0UmRZQ0xSeHpDCkpBY25ZMUZDSWRrRzhWcFZPSkZFVnBnWkNFMGRQTldEdHM5cTRyaUR3NXNodWVHd2RldXdoSytwenhQNmlDUmsKNEdER3hzT0hnUERkNy9vVUxzYm9EaEJCT3lOb0VyL2kvWjVQOHpzc1psR20rY2FnTTJETG1oNkxONUlVaTUzWgptNEdHTi81NEN5Zk5pMUFFUitWazlMOTNzOWNkODJuZnlEMkZ3QXNZdkZRcEFRL2c1ekROZ3NsUHZYeUR6OGo1CnNLQmRzcXdnVG53bkFvSUJBQXkxdUIzbjdIMU1ydy8wd3krN0gzRUlBdkhsT2x3K1JvcjVHdlhiSjNSY0hFT3UKaDluSXI2K0NlWVE3QjVxV0RBeDQ0SDc2L25JZ0dTNXFrR1lMdGwySmhsTThkd1d6NWZMNGNBUEFJQkgzT0R0dgpCUnMyejFmWE5XZlA1WjkrZU1kVlBSTVBnTzdMcE41YlkwSWFDLzlhbWJYazJJYVNpYm5TN0dLakhFMFhqYkdPClQxNVJmUGcwY2VpeW9GWGdLckRkelhqRllvM1pWQVVybVUwdkFYdTJyQktKMWR3bnFjN1R6bjVDd1ZKaUJJSE0KR001Nm1mQmNpOUZ1ditnV1BweFJ3WTdtZDNyalVqbGdlK2FGNy84VGxvTFFVR1hQSm1UUHk0YTFmSlFKWkV1MQphcmFUUWJVNUQrbE4zVEtOc3VDblJZNlcwaDIwRE5jZnFFTmhyWGtDZ2dFQVdIN1FxMkkzdnBaeGNwRWo5ZWpECjJFa2k5VnRDQXBMaE1OdE52NGU2WHRVaGFJTURnMEhHWS9WRmgrRUo4ZEl2ZFlGQXhidkxHS1NFQWQrRFJOdTYKbjNvc3RFUDlsVlJtaGxEOEdmelBJNTA3RkZ0WWVVdk9jQTZkVzZ2WEFUSUdIaWs2Tm1maHFrajA3U1gxQU84OQpWYlArRVN5c04xdWpEeXV1VUtOTTlqbStYTGlsWHMxOS8xaTRJZk5VbXg3TzRXUkpEQWJFakRkMktZYkFGU09kCmNBVWd4L09XVEw0bVJQUDlzQnNtWk9pTVhuS01IYmZiSHEyNkpLU3dWVDUzSXVxeG9FQW96U1FFVHNEUWVUY2QKd3BSc0dsMlRrVjJtc1NxMC95ZzBPbkdzZ2ZSRlJLSGFWWEJOSXZwcVM5bHpJd1VlWXMxaWxXZGZLb1F4SlJBYwpyd0tDQVFCemdWeFZxYTV0T0ZudzhRbWZVWU1lN0RIQ1U0cjNSUzFPTndtR29YSTFSTHp6M0k4U1JHSWJOcFYxCnlJczRnRldXd0l1WG40ekxvMCtZZExwT2prRmg1S2FrMEVya2g3QjUvWm01OWZkR013dWpBMnZpUUdZalJyek8Ka1RTQ1hQZ3JHd0s5QmxqWWZlbFM5cVd1aTl2RHVSaEFXUVpPT0NDeVB0eEVjT3ZyOXFmOUtoT2MweEVFTnRVagp6L01CSDc4NnJwckJFQVhuT0FGRkpibWZ0TFhZeTlSaEFhdTJTTURYMGc5dWRIRE1RTk9Cb1dPN2RoLzVBNXZhCkxMa3BWZ3ZvWWtjU1NjRGFKSUtzb2RQTGNManFYWGQ1MVhOV3BDOWNPWkJaUVM4RXVOMVZmR3JqT0RZOW1SOGIKakNvbUgxUDBGenlQVm1MU2JvV21qRGJzMFNGZQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==