Files
bluejay-infra/apps/telephony/telephony.yaml

350 lines
14 KiB
YAML

# FlowerCore.Telephony - Blazor Server + REST API + Twilio IVR
# ArgoCD managed - BlueJay Lab
# Credentials: 1Password → OnePasswordItem CRD → K8s Secret (twilio-credentials)
# TTS: Piper on edge1 (10.0.57.15:8500)
# Public: telephony.flowercore.io via Cloudflare origin cert
---
apiVersion: v1
kind: Namespace
metadata:
name: telephony
labels:
app.kubernetes.io/part-of: bluejay-infra
---
# Cloudflare Origin Certificate for *.flowercore.io + *.iamwork.in (15-year RSA)
apiVersion: v1
kind: Secret
metadata:
name: cf-origin-flowercore-io
namespace: telephony
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVvRENDQTRpZ0F3SUJBZ0lVSXN4c1NKV1VRL0tqZ09ldk81YnNuVi9rZVE4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZc3hDekFKQmdOVkJBWVRBbFZUTVJrd0Z3WURWUVFLRXhCRGJHOTFaRVpzWVhKbExDQkpibU11TVRRdwpNZ1lEVlFRTEV5dERiRzkxWkVac1lYSmxJRTl5YVdkcGJpQlRVMHdnUTJWeWRHbG1hV05oZEdVZ1FYVjBhRzl5CmFYUjVNUll3RkFZRFZRUUhFdzFUWVc0Z1JuSmhibU5wYzJOdk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGgKTUI0WERUSTJNRE14TURFMk16TXdNRm9YRFRReE1ETXdOakUyTXpNd01Gb3dZakVaTUJjR0ExVUVDaE1RUTJ4dgpkV1JHYkdGeVpTd2dTVzVqTGpFZE1Cc0dBMVVFQ3hNVVEyeHZkV1JHYkdGeVpTQlBjbWxuYVc0Z1EwRXhKakFrCkJnTlZCQU1USFVOc2IzVmtSbXhoY21VZ1QzSnBaMmx1SUVObGNuUnBabWxqWVhSbE1JSUJJakFOQmdrcWhraUcKOXcwQkFRRUZBQU9DQVE0QU1JSUJDZ0tDQVFFQXV0QmpkQ0xEdHdMQlZCU0Y1ZU1OMkt3ckIxTmZmRVhRMjlRRAo1aVR0dzJFcEZXNVJJSllkMjNrYUpCMU5jZXpHWlg4a0Q0cGEyWHpFZW1MVEtJNWw0MU11b3FoWjczNVE3U3RWCkVjRFFTT2ZYTkZQdFMwb0hqb0pRdGF2QjM0ZmJNR3l4Mmx0MU9HUzRNMGtLUWpBNWR6OTJQYjNyZ1RKR0JhOW4KeTZtVThncjRuUHRSdklxZ3NxdjRtMFA3dVU1YjE3NzU1Y2JLSDVoMzIxWHVjMDU4Tzl4M2JHQ0NuRUJXWDdqeApjRGhkUEs1Ri9XRjVBQnl5cFhIQ0ZxUUd4M1NVbmtCQ0ZQSmRabnMra3BHVUZWZGhud3B6NjBtNnlJSzQ0eVR4CjZqR3JOTFEyM1dOK2gwU1lCZU5vb2JBWThydkpiVlZEaGJqSVhBTWtFNGQzVll1TlhRSURBUUFCbzRJQklqQ0MKQVI0d0RnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXTUJRR0NDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjRApBVEFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCUkt1NkJVUDZ0N2dpbFRPay9FdEdKQ3R6N3dTREFmCkJnTlZIU01FR0RBV2dCUWs2Rk5YWFh3MFFJZXA2NVRidXVFV2VQd3BwREJBQmdnckJnRUZCUWNCQVFRME1ESXcKTUFZSUt3WUJCUVVITUFHR0pHaDBkSEE2THk5dlkzTndMbU5zYjNWa1pteGhjbVV1WTI5dEwyOXlhV2RwYmw5agpZVEFqQmdOVkhSRUVIREFhZ2d3cUxtbGhiWGR2Y21zdWFXNkNDbWxoYlhkdmNtc3VhVzR3T0FZRFZSMGZCREV3Ckx6QXRvQ3VnS1lZbmFIUjBjRG92TDJOeWJDNWpiRzkxWkdac1lYSmxMbU52YlM5dmNtbG5hVzVmWTJFdVkzSnMKTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDSjMvTGNleE5pb0lWdUxoemhmbTZCeDV2SWk3T25CaHF1WUlDdwplNnArZ0prdE16ZFJQcDV0bk03dllBWmxMajVJOTByWDRuczhJc3dEbzJBN2wwYTRGZVJFclFmRklsZXQzbjIyCjUxVTZYVElCSks5c1FZT0FkU3pJUzV1OUNKSFpBUTF5WmxSd3BBR3RVWnhxL1dpcGFWUTRwNXhrcEJNMVlZSlAKNW1jQ09HcFErSnpORlpQc2daYUJncDBYL1BBZkNJRkkyZld5QWE2elBqRm0rdDVXUXIrZlBaT2VUS2VIbWVzVgo3UlZxUUdEb3Q0eTY1NklEdmdmU2ZLRnFIRW9XNDJVbDBxQ05hMS9keEJld3NIS1VWWE1ETkdiQlNVQjM4TG9YCm1OQ3hJQlVOUjR0TG1CQUxZT3hVMnZhSWRCd0xBc2YrcndnVnVjUGpCUTc2VWMwUQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzYwR04wSXNPM0FzRlUKRklYbDR3M1lyQ3NIYTE5OFJkRGIxQVBtSk8zRFlTa1ZibEVnbGgzYmVSb2tIVTF4N01abGZ5UVBpbHJaZk1SNgpZdE1vam1YalV5NmlxRm52ZmxEdEsxVVJ3TkJJNTljMFUrMUxTZ2VPZ2xDMXE4SGZoOXN3YkxIYVczVTRaTGd6ClNRcENNRGwzUDNZOXZldUJNa1lGcjJmTHFaVHlDdmljKzFHOGlxQ3lxL2liUS91NVRsdlh2dm5seHNvZm1IZmIKVmU1elRudzczSGRzWUlLY1FGWmZ1UEZ3T0YwOHJrWDlZWGtBSExLbGNjSVdwQWJIZEpTZVFFSVU4bDFtZXo2UwprWlFWVjJHZkNuUHJTYnJJZ3JqakpQSHFNYXMwdERiZFkzNkhSSmdGNDJpaHNCanl1OGx0VlVPRnVNaGNBeVFUCmgzZFZpNDFkQWdNQkFBRUNnZ0VBTGlseXZkNmVTcEYvZUxtV2lhTVV4NUxwa2dhWHpITkxCQnNNZUpqcytLL0EKVVdlZ1crTkVUdmlLalZ5QlI5SzRocG1IYldDa2lPUDBBQUwrQnlKQ3lvekNOQmJTSEdRejlwc1R5dzZBV1ZlUwpuYjlVWGx1VmFQRktKTTRqbXNydERuYjVic25WT2lGblErTDdTalkwNlFMUlFybjBvUWp0ZFJldUdBMFlQVU90CkhSYzNsMFg2ZHJqdkJYY2prWTQwWm9ZYkRrelJnU1JWbWVOUGFIbjZPR0NtYUVUMXVyK01qYVZ2ME9lbEdIWncKVzljSEIxaHNxRzUvMWU3V0RQN0l0cjkwTmg4ay81NVhiK3lQUnhsRFd5bWtZMzIvdFBtZzdESTRKV2tRRWt3cgpIZUtwODVTcE5ta1liRnVpVFppeU8zZDZ0aXZHNHhFZW8rSzFVVFU4c1FLQmdRRFRNSEU1RDFYVC9HbGR5VHNsCllrODRVL1N0NXUrK2RIUEt1Wmw2dVB0UGgxV1lrdnFRcmdrL05YanVud2xGN0Y3b2tWOGdPeWxreTYwYTZkcXIKeXZwN1ZJdXYzekVlc2h2NjNWMlpaVkMzcXZYSzFheit3Zmx3NitCZmVuRlY5S2NENHN0dTdwOFRPWmFGN01CUgo3YXZzaXVXbWtqdmM1TlVLRmVDRTY0SnZFUUtCZ1FEaWMrbWlNLzBodDN1ajhuOXgyMDFQZFNqbEpVaUc1NjNNCnRYZlBCdDJRT0NhaVluUFNFdTdXdm5pQWRFL2xrMm91cFRWam9LYmZPbDFyQjd6UzVhc2kxdVdDZDhlUy9UWGIKdU5iRmlNMDB4L3JxalMydCtQbTd4MVhrYTB4TFNSRDNmZ0tSQldSN3pscStkYWZ1WE1qelUxRnh5dTIycGphRgpIMEl3NEpCUmpRS0JnUUNOaWhMb0Rob1V5RCtKNXJzb00vb3FJMEtDWnB0WlJzendHbkg5cVFwdFk2Ti9iVXBYCk92emhpeUh3czAvUXVEbG5uejVrNktHMmR6Y2VLWXN2eGdzWUt6S3ZmV043VWgya2hVWWM3NlVvWTREMkh6MGgKUkxtNzc2cGg4enNRUTdiSHlQRlUrTUpPYlRNdnNOdTRUUlVEcEplRGl0QnFIRWVYeWMrKzVlUjJNUUtCZ0h2UgptVHVoWlpVYitEVEtrVGkyQ20yWnlBU1RBRGNUVW9xTjVyYUNNSDk4MUZNUnRmWjFkN1pmYXhBQmlQWWtSbmkrCnlKUnk4UXM1cEg2ek9tR3VSb2JFTGJYS3ZJcjRmSXhwWXJXYmVXaVV0L09yd2dCUUZHekNMNHEzeUgyWnMvYy8KSlRRYVdMa0JPY2pPR0VaUzRXVjZkeHZiTTJNZE9zNUxLeXdDZmFhNUFvR0FIQUE1eEN0dndOZE4xeExndkZ3RApPK2lyMDl1bXMxOFBzSVpmK1ZrWGtpcHF4MWNUT0hEanpPR01yWXV0M2FFeE00Zjd2ckFHRFMyY2pwZjM0T1JxCit4Y2gwWlNaQ2FDZmlnZG9OelNkcDFLcmo0cnFKdG5ZdS9CNDlDQlVoSDBNaCtSRWswQ0hHOVE4b3FOWFk0V0wKbVVOVTZMYUkwQWtvSzNVb2tWQVJEYXM9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
---
# 1Password → K8s Secret sync for Twilio credentials
# Creates secret "twilio-credentials" with fields: AccountSid, AuthToken, DefaultFromNumber
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: twilio-credentials
namespace: telephony
spec:
itemPath: "vaults/IAmWorkin/items/Twilio Account"
---
# Application configuration overlay
apiVersion: v1
kind: ConfigMap
metadata:
name: telephony-config
namespace: telephony
data:
appsettings.Production.json: |
{
"Telephony": {
"Provider": "asterisk",
"Twilio": {
"VoiceUrl": "https://telephony.flowercore.io/api/twilio/webhooks/voice/incoming",
"StatusCallbackUrl": "https://telephony.flowercore.io/api/twilio/webhooks/voice/status"
},
"Asterisk": {
"BaseUrl": "http://localhost:8088",
"Username": "flowercore",
"Password": "bluejay-asterisk-ari",
"Application": "flowercore-pbx",
"ReconnectDelaySeconds": 5,
"MaxReconnectDelaySeconds": 60
}
},
"Ari": {
"BaseUrl": "http://localhost:8088",
"Username": "flowercore",
"Password": "bluejay-asterisk-ari",
"Application": "flowercore-pbx",
"ReconnectDelaySeconds": 5,
"MaxReconnectDelaySeconds": 60
},
"Tts": {
"PiperUrl": "http://10.0.57.15:8500",
"DefaultEngine": "piper",
"SampleRate": 8000
},
"DatabaseProvider": "Sqlite",
"ConnectionStrings": {
"DefaultConnection": "Data Source=/data/telephony.db"
},
"Kestrel": {
"Endpoints": {
"Http": { "Url": "http://0.0.0.0:5100" }
}
}
}
---
# Persistent volume for SQLite database
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: telephony-data
namespace: telephony
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
---
# Telephony web application
apiVersion: apps/v1
kind: Deployment
metadata:
name: telephony-web
namespace: telephony
labels:
app: telephony-web
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: telephony-web
template:
metadata:
labels:
app: telephony-web
spec:
securityContext:
fsGroup: 1654
initContainers:
- name: fix-data-perms
image: busybox:latest
command: ["sh", "-c", "chown -R 1654:1654 /data"]
volumeMounts:
- name: telephony-data
mountPath: /data
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: asterisk
topologyKey: kubernetes.io/hostname
containers:
- name: telephony-web
image: localhost/fc-telephony-web:v20260324l
imagePullPolicy: Never
ports:
- containerPort: 5100
name: http
env:
- name: Telephony__Twilio__AccountSid
valueFrom:
secretKeyRef:
name: twilio-credentials
key: AccountSid
optional: true
- name: Telephony__Twilio__AuthToken
valueFrom:
secretKeyRef:
name: twilio-credentials
key: AuthToken
optional: true
- name: Telephony__Twilio__DefaultFromNumber
valueFrom:
secretKeyRef:
name: twilio-credentials
key: DefaultFromNumber
optional: true
volumeMounts:
- name: telephony-config
mountPath: /app/appsettings.Production.json
subPath: appsettings.Production.json
readOnly: true
- name: telephony-data
mountPath: /data
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
cpu: "1"
livenessProbe:
httpGet:
path: /health
port: 5100
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 5100
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: telephony-config
configMap:
name: telephony-config
- name: telephony-data
persistentVolumeClaim:
claimName: telephony-data
---
# ClusterIP service
apiVersion: v1
kind: Service
metadata:
name: telephony-web
namespace: telephony
spec:
selector:
app: telephony-web
ports:
- port: 5100
targetPort: 5100
name: http
---
# Traefik IngressRoute — public via Cloudflare (primary: flowercore.io)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: telephony-web
namespace: telephony
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`telephony.flowercore.io`)
services:
- name: telephony-web
port: 5100
- kind: Rule
match: Host(`telephony.iamwork.in`)
services:
- name: telephony-web
port: 5100
tls:
secretName: cf-origin-flowercore-io
---
# NetworkPolicy: deny-all baseline + Traefik ingress + SIP/RTP ingress + DNS egress + TTS egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: telephony-netpol
namespace: telephony
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
# Allow Traefik ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
# Allow Selenium Grid for automated UI testing
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: selenium
ports:
- port: 5100
protocol: TCP
# Allow SIP/RTP from external sources (Yealink phones, Twilio SIP trunk)
- from:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- port: 5060
protocol: UDP
- port: 5060
protocol: TCP
- port: 10000
endPort: 20000
protocol: UDP
egress:
# Allow DNS resolution (CoreDNS in kube-system)
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# Allow Piper TTS on edge1 (10.0.57.15:8500)
- to:
- ipBlock:
cidr: 10.0.57.15/32
ports:
- port: 8500
protocol: TCP
# Allow Twilio API outbound (HTTPS)
- 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: 443
protocol: TCP
# Allow SIP/RTP responses (Asterisk → phones and Twilio)
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- port: 5060
protocol: UDP
- port: 5060
protocol: TCP
- port: 10000
endPort: 20000
protocol: UDP
# Allow 1Password Connect for secret sync
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: onepassword-system
---
# TLS Certificate for internal hostname via cert-manager
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: telephony-internal-tls
namespace: telephony
spec:
secretName: telephony-internal-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- telephony.iamworkin.lan
---
# Traefik IngressRoute — internal LAN access
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: telephony-web-internal
namespace: telephony
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`telephony.iamworkin.lan`)
services:
- name: telephony-web
port: 5100
tls:
secretName: telephony-internal-tls