mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
fix(deploy): address security and correctness blockers from PR review
- gcp/cloudrun-service.yaml: add comment + README sed one-liner so PROJECT_ID
is substituted before gcloud run services replace (was a literal placeholder
that caused image-pull failure on the declarative deploy path)
- azure/main.parameters.json: replace wildcard allowedOrigins "*" with a
REPLACE_ME placeholder; add README note to set the real URL after first deploy
- kubernetes/networkpolicy.yaml + helm networkpolicy template: add from: selector
(ingress-nginx namespace + same-namespace pods) so ingress is no longer
allow-all; restrict egress to FalkorDB port 6379 and DNS port 53 instead of
the allow-all egress: - {} wildcard
- helm/values.yaml: expose networkPolicy.ingressNamespace and falkordbPort values
- kubernetes/deployment.yaml: add secretRef for knowledge-explorer-secrets so
FALKORDB_PASSWORD is actually injected into the container
- app.py: add _mutation_bridge_installed guard to prevent closure stacking when
the same GraphSession is passed to create_app() more than once; remove
duplicate app.state.allowed_origins assignment (single source of truth is
app.state.explorer_settings); add comment on falkordb_host/port dead config
- tests: update allowed_origins assertions to use explorer_settings dict
- .checkov.yaml: remove global CKV_K8S_21/28/30 suppressions; rely on per-file
inline checkov:skip comments in cloudrun-service.yaml so future real K8s
manifests are not silently exempted
This commit is contained in:
+4
-17
@@ -1,17 +1,4 @@
|
|||||||
# Checkov configuration — applies when running checkov locally or via the
|
# Checkov configuration.
|
||||||
# bridgecrewio/checkov-action in CI.
|
# Cloud Run false-positives (CKV_K8S_21/28/30) are suppressed via per-file
|
||||||
#
|
# inline checkov:skip comments in deploy/gcp/cloudrun-service.yaml rather than
|
||||||
# The rules below are suppressed because deploy/gcp/cloudrun-service.yaml uses
|
# globally here, so real Kubernetes manifests are not silently exempted.
|
||||||
# apiVersion: serving.knative.dev/v1 which checkov scans as a Kubernetes resource.
|
|
||||||
# Cloud Run is a fully managed platform — K8s namespace, AppArmor, and seccomp
|
|
||||||
# concepts do not apply to it.
|
|
||||||
#
|
|
||||||
# CKV_K8S_21 — "default namespace should not be used": Cloud Run services have no
|
|
||||||
# namespace concept; checkov sees the absence of metadata.namespace
|
|
||||||
# as using the "default" namespace.
|
|
||||||
# CKV_K8S_28 — seccomp profile: Cloud Run enforces this at the platform level.
|
|
||||||
# CKV_K8S_30 — AppArmor profile: Cloud Run enforces this at the platform level.
|
|
||||||
skip-check:
|
|
||||||
- CKV_K8S_21
|
|
||||||
- CKV_K8S_28
|
|
||||||
- CKV_K8S_30
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ azd env set AZURE_INFRASTRUCTURE_SUBNET_ID /subscriptions/<sub>/resourceGroups/<
|
|||||||
azd up
|
azd up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
After the first deploy, set `allowedOrigins` in `main.parameters.json` to the Container App URL printed by `azd up` (e.g. `https://<app-name>.<unique>.eastus.azurecontainerapps.io`), then re-run `azd up` to apply the CORS restriction.
|
||||||
|
|
||||||
The Bicep template provisions:
|
The Bicep template provisions:
|
||||||
|
|
||||||
- A Container Apps managed environment with an internal load balancer (private VNet, no public IP) and a system-assigned managed identity on the Container App (AZR-000363 / AZR-000361 compliant).
|
- A Container Apps managed environment with an internal load balancer (private VNet, no public IP) and a system-assigned managed identity on the Container App (AZR-000363 / AZR-000361 compliant).
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"value": "${SERVICE_EXPLORER_IMAGE_NAME}"
|
"value": "${SERVICE_EXPLORER_IMAGE_NAME}"
|
||||||
},
|
},
|
||||||
"allowedOrigins": {
|
"allowedOrigins": {
|
||||||
"value": "*"
|
"value": "https://REPLACE_ME.azurecontainerapps.io"
|
||||||
},
|
},
|
||||||
"falkordbHost": {
|
"falkordbHost": {
|
||||||
"value": "falkordb"
|
"value": "falkordb"
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ gcloud builds submit --config deploy/gcp/cloudbuild.yaml \
|
|||||||
--substitutions _REGION=us-central1,_SERVICE_NAME=knowledge-explorer,_ALLOWED_ORIGINS=https://knowledge-explorer-REPLACE_ME.a.run.app
|
--substitutions _REGION=us-central1,_SERVICE_NAME=knowledge-explorer,_ALLOWED_ORIGINS=https://knowledge-explorer-REPLACE_ME.a.run.app
|
||||||
```
|
```
|
||||||
|
|
||||||
For declarative deploys, replace `PROJECT_ID` in `cloudrun-service.yaml`, then run:
|
For declarative deploys, substitute your project ID and deploy in one step:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud run services replace deploy/gcp/cloudrun-service.yaml --region us-central1
|
sed "s/PROJECT_ID/$(gcloud config get-value project)/g" deploy/gcp/cloudrun-service.yaml | \
|
||||||
|
gcloud run services replace - --region us-central1
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ spec:
|
|||||||
timeoutSeconds: 300
|
timeoutSeconds: 300
|
||||||
containers:
|
containers:
|
||||||
- name: explorer
|
- name: explorer
|
||||||
|
# Replace PROJECT_ID with your GCP project ID before deploying.
|
||||||
|
# See the README for the sed one-liner that does this automatically.
|
||||||
image: gcr.io/PROJECT_ID/knowledge-explorer:latest
|
image: gcr.io/PROJECT_ID/knowledge-explorer:latest
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
|
|||||||
@@ -13,9 +13,30 @@ spec:
|
|||||||
- Ingress
|
- Ingress
|
||||||
- Egress
|
- Egress
|
||||||
ingress:
|
ingress:
|
||||||
- ports:
|
# Allow traffic from the ingress controller namespace.
|
||||||
|
# Override networkPolicy.ingressNamespace in values if your controller uses a different namespace.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ .Values.networkPolicy.ingressNamespace }}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.service.targetPort }}
|
||||||
|
# Allow traffic from pods within the same namespace (e.g. monitoring sidecars).
|
||||||
|
- from:
|
||||||
|
- podSelector: {}
|
||||||
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: {{ .Values.service.targetPort }}
|
port: {{ .Values.service.targetPort }}
|
||||||
egress:
|
egress:
|
||||||
- {}
|
# FalkorDB
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.networkPolicy.falkordbPort | default 6379 }}
|
||||||
|
# DNS resolution
|
||||||
|
- ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -96,3 +96,8 @@ affinity: {}
|
|||||||
|
|
||||||
networkPolicy:
|
networkPolicy:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# Namespace label of your ingress controller. Ingress is only admitted from this namespace
|
||||||
|
# and from pods within the same namespace as the Explorer.
|
||||||
|
ingressNamespace: ingress-nginx
|
||||||
|
# FalkorDB port allowed for egress. Must match FALKORDB_PORT.
|
||||||
|
falkordbPort: 6379
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ spec:
|
|||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: knowledge-explorer-config
|
name: knowledge-explorer-config
|
||||||
|
- secretRef:
|
||||||
|
name: knowledge-explorer-secrets
|
||||||
|
optional: true
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /api/health
|
path: /api/health
|
||||||
|
|||||||
@@ -14,8 +14,29 @@ spec:
|
|||||||
- Ingress
|
- Ingress
|
||||||
- Egress
|
- Egress
|
||||||
ingress:
|
ingress:
|
||||||
- ports:
|
# Allow traffic from the ingress controller namespace.
|
||||||
|
# Adjust the namespace label if your ingress controller uses a different namespace.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: ingress-nginx
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8000
|
||||||
|
# Allow traffic from pods within the same namespace (e.g. monitoring sidecars).
|
||||||
|
- from:
|
||||||
|
- podSelector: {}
|
||||||
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8000
|
port: 8000
|
||||||
egress:
|
egress:
|
||||||
- {}
|
# FalkorDB
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 6379
|
||||||
|
# DNS resolution
|
||||||
|
- ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
|||||||
@@ -40,12 +40,18 @@ def _read_explorer_settings() -> dict:
|
|||||||
"allowed_origins": [
|
"allowed_origins": [
|
||||||
origin.strip() for origin in raw_origins.split(",") if origin.strip()
|
origin.strip() for origin in raw_origins.split(",") if origin.strip()
|
||||||
],
|
],
|
||||||
|
# These are read and stored for future use when direct FalkorDB connection
|
||||||
|
# support is added to the Explorer. Currently GraphSession uses an in-memory
|
||||||
|
# ContextGraph and does not open a network connection to FalkorDB.
|
||||||
"falkordb_host": os.environ.get("FALKORDB_HOST", "localhost"),
|
"falkordb_host": os.environ.get("FALKORDB_HOST", "localhost"),
|
||||||
"falkordb_port": _read_int_env("FALKORDB_PORT", 6379),
|
"falkordb_port": _read_int_env("FALKORDB_PORT", 6379),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _install_mutation_bridge(app: FastAPI, session: GraphSession) -> None:
|
def _install_mutation_bridge(app: FastAPI, session: GraphSession) -> None:
|
||||||
|
if getattr(session.graph, "_mutation_bridge_installed", False):
|
||||||
|
return
|
||||||
|
session.graph._mutation_bridge_installed = True
|
||||||
previous_callback = getattr(session.graph, "mutation_callback", None)
|
previous_callback = getattr(session.graph, "mutation_callback", None)
|
||||||
|
|
||||||
def on_mutation(event_type: str, entity_id: str, payload: dict) -> None:
|
def on_mutation(event_type: str, entity_id: str, payload: dict) -> None:
|
||||||
@@ -89,7 +95,6 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI:
|
|||||||
)
|
)
|
||||||
|
|
||||||
app.state.explorer_settings = settings
|
app.state.explorer_settings = settings
|
||||||
app.state.allowed_origins = settings["allowed_origins"]
|
|
||||||
|
|
||||||
# allow_credentials lets browsers send cookies/auth headers cross-origin.
|
# allow_credentials lets browsers send cookies/auth headers cross-origin.
|
||||||
# The Explorer has no authentication, so credentials serve no purpose and
|
# The Explorer has no authentication, so credentials serve no purpose and
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class TestHealthInfo:
|
|||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
assert app.state.allowed_origins == [
|
assert app.state.explorer_settings["allowed_origins"] == [
|
||||||
"https://app.example.com",
|
"https://app.example.com",
|
||||||
"https://team.example.com",
|
"https://team.example.com",
|
||||||
]
|
]
|
||||||
@@ -184,7 +184,7 @@ class TestHealthInfo:
|
|||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
assert app.state.allowed_origins == ["https://legacy.example.com"]
|
assert app.state.explorer_settings["allowed_origins"] == ["https://legacy.example.com"]
|
||||||
|
|
||||||
def test_default_app_initializes_empty_graph_session(self):
|
def test_default_app_initializes_empty_graph_session(self):
|
||||||
with TestClient(create_app()) as test_client:
|
with TestClient(create_app()) as test_client:
|
||||||
|
|||||||
Reference in New Issue
Block a user