diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 9a39b68..e19fa67 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: socket-firewall description: Socket.dev Registry Firewall - Block vulnerable packages before they reach your cluster type: application -version: 0.11.4 +version: 0.12.0 # appVersion is the single source of truth for the firewall image version. # image.tag in values.yaml defaults to this (see templates/_helpers.tpl). appVersion: "2.3.3" diff --git a/helm/README.md b/helm/README.md index 0af7e04..5bc31e6 100644 --- a/helm/README.md +++ b/helm/README.md @@ -244,6 +244,31 @@ helm install socket-firewall . -f examples/remote-first.yaml \ --set ingress.hosts[0].host=sfw.yourcompany.com ``` +## Validating Your Configuration + +The chart validates values at three points: + +1. **Schema validation** (`values.schema.json`) runs automatically on every + `helm install`, `helm upgrade`, `helm lint`, and `helm template`. It checks + types and rejects unknown keys. +2. **Render-time guards** (`templates/validations.yaml`) fail the render with an + actionable message for configurations that would deploy but not work: + path routing enabled with no routes, an ElastiCache cluster-mode + (`clustercfg.*`) Redis endpoint, or no Socket API token / existing secret. +3. **Post-install tests** verify the live deployment — health endpoint, egress + to the Socket API, Redis connectivity (via `/metrics`), and route matching: + + ```bash + helm test -n + ``` + +> **Strictness change:** unknown top-level keys in your values now fail +> validation instead of being silently ignored. This is deliberate — it catches +> indentation accidents (e.g. a `pathRouting:` block whose children slip to the +> top level and leave the firewall with zero routes) and dead config (e.g. an +> `env:` block no template reads). If you need to pass config keys the chart +> doesn't expose, use `extraConfig` (raw `socket.yml` passthrough). + ## Proxy Modes ### Path-Based Routing (Recommended) diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index b63a674..3da25c7 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -137,4 +137,36 @@ Set metrics.serviceMonitor.enabled=true (Prometheus Operator) or metrics.podAnno {{- end }} {{- end }} +{{- $socket := .Values.socket | default dict }} +{{- $ingress := .Values.ingress | default dict }} +{{- $albScheme := index ($ingress.annotations | default dict) "alb.ingress.kubernetes.io/scheme" | default "" }} +{{- $noClientAuth := and (not $socket.bearerToken) (not $socket.bearerTokenExistingSecret) (not $socket.basicAuthUsername) }} +{{- $openProxy := and $ingress.enabled (eq $albScheme "internet-facing") $noClientAuth }} +{{- if or $socket.failOpen $openProxy }} + +## Warnings +{{- if $socket.failOpen }} + +- socket.failOpen is true: whenever the Socket API is unreachable, packages are + allowed through UNSCANNED. Set socket.failOpen=false for fail-closed behavior + (blocks all packages while the API is down). +{{- end }} +{{- if $openProxy }} + +- The ALB ingress scheme is "internet-facing" and no client auth is configured + (socket.bearerToken, socket.bearerTokenExistingSecret, and + socket.basicAuthUsername are all empty). The firewall will accept + unauthenticated internet traffic and can be used as an open proxy to the + upstream registries. Enable the client auth gate (see "Client Auth Gate" in + README.md) and/or restrict the ALB's security groups to trusted CIDRs. +{{- end }} +{{- end }} + +## Validate + +Run the chart's built-in checks against the live deployment +(health endpoint, Socket API egress, Redis, and route matching): + + helm test {{ .Release.Name }} -n {{ .Release.Namespace }} + See deployment recommendations in README.md for production setup. diff --git a/helm/templates/tests/test-connection.yaml b/helm/templates/tests/test-connection.yaml new file mode 100644 index 0000000..a582c80 --- /dev/null +++ b/helm/templates/tests/test-connection.yaml @@ -0,0 +1,80 @@ +{{- $fullname := include "socket-firewall.fullname" . -}} +{{- $apiUrl := (.Values.socket).apiUrl | default "https://api.socket.dev" -}} +{{- $healthPath := (.Values.healthCheck).path | default "/health" -}} +{{- $pathRouting := .Values.pathRouting | default dict -}} +apiVersion: v1 +kind: Pod +metadata: + name: {{ $fullname }}-test-connection + labels: + {{- include "socket-firewall.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + restartPolicy: Never + containers: + - name: test-connection + image: curlimages/curl:8.11.1 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsNonRoot: true + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + command: + - /bin/sh + - -c + - | + set -u + BASE="http://{{ $fullname }}:{{ ((.Values.service).httpPort) | default 80 }}" + fail() { + echo "FAILED: $1" + exit 1 + } + + echo "==> [1] Health endpoint: GET ${BASE}{{ $healthPath }}" + code=$(curl -s --max-time 15 -o /tmp/health.out -w '%{http_code}' "${BASE}{{ $healthPath }}") || true + [ "$code" = "200" ] || fail "health check returned HTTP ${code} (expected 200). The firewall Service or pods are not serving traffic." + grep -q "Health OK" /tmp/health.out || fail "health check returned 200 but the body does not contain 'Health OK'." + echo " OK (HTTP 200, body contains 'Health OK')" + + echo "==> [2] Socket API egress: GET {{ $apiUrl }}/v0/quota (expect HTTP 401)" + code=$(curl -s --max-time 15 -o /dev/null -w '%{http_code}' "{{ $apiUrl }}/v0/quota") || true + if [ "$code" != "401" ]; then + echo " Hint: HTTP 000 or a timeout means the cluster cannot reach the Socket API" + echo " (egress blocked, DNS failure, or TLS interception). The firewall needs" + echo " outbound HTTPS to {{ $apiUrl }} to fetch package verdicts." + fail "Socket API returned HTTP ${code} (expected exactly 401 — an unauthenticated 401 proves egress and TLS work)." + fi + echo " OK (HTTP 401 — egress and TLS to the Socket API verified)" + {{- if and (eq (include "socket-firewall.metricsExposed" .) "true") ((.Values.redis).enabled) }} + + echo "==> [3] Redis connectivity: GET http://{{ $fullname }}:{{ ((.Values.metrics).port) | default 9145 }}/metrics (expect redis_available 1)" + curl -s --max-time 15 -o /tmp/metrics.out "http://{{ $fullname }}:{{ ((.Values.metrics).port) | default 9145 }}/metrics" || fail "could not fetch /metrics from the firewall." + if ! grep -q '^redis_available 1' /tmp/metrics.out; then + echo " Hint: redis_available 0 means the firewall cannot reach Redis —" + echo " check the endpoint, TLS settings (redis.ssl / CA cert), and auth" + echo " (redis.password / redis.existingSecret)." + fail "metrics do not report 'redis_available 1'." + fi + echo " OK (redis_available 1)" + {{- end }} + {{- if and $pathRouting.enabled $pathRouting.routes }} + {{- $firstRoute := first $pathRouting.routes }} + + echo "==> [4] Path routing: GET ${BASE}{{ $firstRoute.path }}/ with Host: {{ $pathRouting.domain }} (expect anything but 404)" + code=$(curl -s --max-time 15 -o /dev/null -w '%{http_code}' -H "Host: {{ $pathRouting.domain }}" "${BASE}{{ $firstRoute.path }}/") || true + if [ "$code" = "404" ]; then + echo " Hint: routing is Host-header based — a 404 means the request did not" + echo " match pathRouting.domain (or allowedDomain) plus a configured route" + echo " path. Verify clients send the same Host the firewall is configured for." + fail "route {{ $firstRoute.path }}/ returned 404 for Host {{ $pathRouting.domain }}." + fi + echo " OK (HTTP ${code})" + {{- end }} + + echo "All checks passed." diff --git a/helm/templates/validations.yaml b/helm/templates/validations.yaml new file mode 100644 index 0000000..6acd62e --- /dev/null +++ b/helm/templates/validations.yaml @@ -0,0 +1,44 @@ +{{- /* +Render-time configuration guards. This file renders no manifests — it only +fails fast on configurations that would deploy but not work. Structural / +type validation lives in values.schema.json; the checks here are the +cross-field rules a schema can't express. + +Note: `fail` is a no-op under `helm lint` (lint mode); these guards enforce +on `helm install`, `helm upgrade`, and `helm template`. +*/ -}} + +{{- $socket := .Values.socket | default dict -}} +{{- $redis := .Values.redis | default dict -}} +{{- $pathRouting := .Values.pathRouting | default dict -}} + +{{- /* Path routing enabled with no route source: the firewall starts fine but + has nothing to match, so every request 404s. Routes can come from the + inline list, a routes CSV file, or private-registry auto-discovery. */ -}} +{{- if $pathRouting.enabled -}} +{{- $hasRoutes := not (empty $pathRouting.routes) -}} +{{- $hasRoutesFile := not (empty $pathRouting.routesFile) -}} +{{- $hasAutoDiscovery := and $pathRouting.privateRegistry (($pathRouting.privateRegistry).enabled) -}} +{{- if not (or $hasRoutes $hasRoutesFile $hasAutoDiscovery) -}} +{{- fail "pathRouting.enabled is true but pathRouting.routes is empty and no routesFile is set. The firewall would deploy with no routes and return 404 for all traffic. Define pathRouting.routes, set pathRouting.routesFile, or enable pathRouting.privateRegistry auto-discovery." -}} +{{- end -}} +{{- end -}} + +{{- /* ElastiCache cluster-mode configuration endpoints (clustercfg.*) hand out + MOVED redirects across shards. The firewall's Redis client is a + single-endpoint client and cannot follow them, so every cache operation + fails at runtime even though the deployment looks healthy. */ -}} +{{- if $redis.enabled -}} +{{- $redisHost := (toString ($redis.host | default "")) | trimAll " " -}} +{{- if hasPrefix "clustercfg." $redisHost -}} +{{- fail (printf "redis.host %q is an ElastiCache cluster-mode configuration endpoint (clustercfg.*). The firewall uses a single-endpoint Redis client and cannot follow the MOVED redirects a cluster-mode-enabled group issues, so caching would fail at runtime. Use a cluster-mode-disabled replication group and point redis.host at its primary endpoint." $redisHost) -}} +{{- end -}} +{{- end -}} + +{{- /* The deployment always mounts SOCKET_SECURITY_API_TOKEN from a Secret, + but the chart only creates that Secret when socket.apiToken is set. + With neither value the pods reference a Secret that doesn't exist and + stay stuck in CreateContainerConfigError. */ -}} +{{- if and (empty $socket.apiToken) (empty $socket.existingSecret) -}} +{{- fail "Set socket.apiToken or socket.existingSecret. The deployment mounts SOCKET_SECURITY_API_TOKEN from a Secret; without either value the pods reference a Secret Helm does not create. If you manage that Secret out of band, set socket.existingSecret to its name." -}} +{{- end -}} diff --git a/helm/values.schema.json b/helm/values.schema.json new file mode 100644 index 0000000..7f5221a --- /dev/null +++ b/helm/values.schema.json @@ -0,0 +1,693 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "socket-firewall Helm chart values", + "description": "Validates values on install/upgrade/lint. The root rejects unknown top-level keys so indentation accidents (e.g. a pathRouting block whose children land at the top level) and dead config (e.g. a stray env: block no template reads) fail fast instead of being silently ignored. Use extraConfig for raw socket.yml passthrough.", + "type": "object", + "additionalProperties": false, + "definitions": { + "ecosystem": { + "type": "string", + "enum": ["npm", "pypi", "maven", "cargo", "rubygems", "nuget", "go", "conda", "openvsx", "huggingface"] + }, + "portLike": { + "type": ["integer", "string"] + }, + "nullablePort": { + "type": ["integer", "string", "null"] + }, + "nullableBool": { + "type": ["boolean", "null"] + }, + "nullableNumber": { + "type": ["number", "null"] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" } + }, + "freeformObject": { + "type": "object" + }, + "objectArray": { + "type": "array", + "items": { "type": "object" } + }, + "decisionLogLevel": { + "type": "string", + "enum": ["", "error", "warn", "notice", "info", "debug"] + }, + "confirmAllowMode": { + "type": "string", + "enum": ["serve_stale", "wait", "confirm_when_degraded", "fail_when_degraded"] + }, + "route": { + "type": "object", + "additionalProperties": false, + "required": ["path", "registry"], + "properties": { + "path": { "type": "string" }, + "upstream": { "type": "string" }, + "registry": { "$ref": "#/definitions/ecosystem" }, + "mode": { "type": "string", "enum": ["rewrite", "proxy"] }, + "upstreamType": { "type": "string", "enum": ["", "artifactory"] }, + "upstreamHost": { "type": "string" }, + "upstreamToken": { "type": "string" }, + "upstreamTokenSecret": { "type": "string" }, + "baseUrl": { "type": "string" }, + "artifactoryRepo": { "type": "string" }, + "cooldownName": { "type": "string" }, + "cooldownEcosystem": { "type": "string" }, + "repoKey": { "type": "string" } + } + }, + "registryEntry": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "domains": { "$ref": "#/definitions/stringArray" }, + "upstream": { "type": "string" }, + "upstreamToken": { "type": "string" }, + "upstreamTokenSecret": { "type": "string" } + } + }, + "containerSecurityContext": { + "type": "object" + } + }, + "properties": { + "global": { "$ref": "#/definitions/freeformObject" }, + "nameOverride": { "type": "string" }, + "fullnameOverride": { "type": "string" }, + "image": { + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { "type": "string" }, + "tag": { "type": "string" }, + "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } + } + }, + "imagePullSecrets": { "$ref": "#/definitions/objectArray" }, + "replicaCount": { "type": "integer" }, + "podDisruptionBudget": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "minAvailable": { "$ref": "#/definitions/portLike" }, + "maxUnavailable": { "$ref": "#/definitions/portLike" } + } + }, + "autoscaling": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "minReplicas": { "type": "integer" }, + "maxReplicas": { "type": "integer" }, + "targetCPUUtilizationPercentage": { "type": "integer" }, + "targetMemoryUtilizationPercentage": { "type": "integer" } + } + }, + "socket": { + "type": "object", + "additionalProperties": false, + "properties": { + "apiToken": { "type": "string" }, + "existingSecret": { "type": "string" }, + "existingSecretKey": { "type": "string" }, + "failOpen": { "type": "boolean" }, + "failOpenUnscanned": { "type": "boolean" }, + "useRemoteConfig": { "type": "boolean" }, + "configRefreshInterval": { "type": "string" }, + "bearerTokenType": { "type": "string", "enum": ["string", "env"] }, + "bearerToken": { "type": "string" }, + "bearerTokenExistingSecret": { "type": "string" }, + "bearerTokenExistingSecretKey": { "type": "string" }, + "saveIgnoreReason": { "type": "boolean" }, + "requestIdHeader": { "type": "string" }, + "exposeUnscannedHeader": { "type": "boolean" }, + "cacheTtl": { "type": "integer" }, + "logLevel": { "type": "string", "enum": ["", "error", "warn", "info", "debug"] }, + "logMaxBodySize": { "type": "integer" }, + "logNoRoute": { "type": "boolean" }, + "debugLoggingEnabled": { "type": "boolean" }, + "rewritePackageUrls": { "type": "boolean" }, + "recentlyPublishedEnabledEcosystems": { + "type": "array", + "items": { "$ref": "#/definitions/ecosystem" } + }, + "apiUrl": { "type": "string" }, + "apiSslVerify": { "type": "boolean" }, + "apiSslCaCert": { "type": "string" }, + "upstreamSslVerify": { "$ref": "#/definitions/nullableBool" }, + "upstreamSslCaCert": { "type": "string" }, + "outboundProxy": { "type": "string" }, + "noProxy": { "type": "string" }, + "deployment": { "type": "string" }, + "basicAuthUsername": { "type": "string" }, + "basicAuthPassword": { "type": "string" }, + "healthApiToken": { "type": "string" }, + "debugUserAgentFilter": { "type": "string" }, + "apiReadTimeout": { "$ref": "#/definitions/nullableNumber" }, + "apiConnectTimeout": { "$ref": "#/definitions/nullableNumber" }, + "apiSendTimeout": { "$ref": "#/definitions/nullableNumber" }, + "blockLogLevel": { "$ref": "#/definitions/decisionLogLevel" }, + "warnLogLevel": { "$ref": "#/definitions/decisionLogLevel" }, + "monitorLogLevel": { "$ref": "#/definitions/decisionLogLevel" }, + "ignoreLogLevel": { "$ref": "#/definitions/decisionLogLevel" }, + "ecosystemParams": { + "type": "object", + "additionalProperties": { "type": "object" } + }, + "resilience": { + "type": "object", + "additionalProperties": false, + "properties": { + "circuitBreaker": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "latencyEwmaThresholdMs": { "type": "number" }, + "ewmaAlpha": { "type": "number" }, + "errorRateThreshold": { "type": "number" }, + "errorMinRequests": { "type": "number" }, + "errorWindowS": { "type": "number" }, + "openWindowS": { "type": "number" }, + "maxOpenWindowS": { "type": "number" }, + "backoffFactor": { "type": "number" }, + "halfopenProbes": { "type": "number" }, + "halfopenProbeSpacingS": { "type": "number" }, + "probeTimeoutMs": { "type": "number" } + } + } + } + } + } + }, + "cache": { + "type": "object", + "additionalProperties": false, + "properties": { + "revalidationLockLeaseSeconds": { "type": "integer" }, + "revalidationJitterSeconds": { "type": "integer" }, + "revalidationAsync": { "type": "boolean" }, + "confirmAllowMode": { "$ref": "#/definitions/confirmAllowMode" }, + "warmEnabled": { "type": "boolean" }, + "warmInterval": { "type": "integer" } + } + }, + "keepaliveTimeout": { "type": "integer" }, + "upstreamTokens": { + "type": "object", + "additionalProperties": false, + "properties": { + "values": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } + }, + "pathRouting": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "domain": { "type": "string" }, + "allowedDomain": { "$ref": "#/definitions/stringArray" }, + "mode": { "type": "string", "enum": ["", "local", "artifactory", "nexus", "downstream"] }, + "configMode": { "type": "string", "enum": ["", "upstream", "middle"] }, + "upstreamFqdn": { "type": "string" }, + "rewriteScheme": { "type": "string", "enum": ["", "http", "https"] }, + "clientRewriteScheme": { "type": "string", "enum": ["", "http", "https"] }, + "useIncomingDomain": { "$ref": "#/definitions/nullableBool" }, + "forwardForDomain": { "$ref": "#/definitions/nullableBool" }, + "overrideLocationRewrite": { "$ref": "#/definitions/nullableBool" }, + "overridePortHttp": { "$ref": "#/definitions/portLike" }, + "overridePortHttps": { "$ref": "#/definitions/portLike" }, + "routesFile": { "type": "string" }, + "routes": { + "type": "array", + "items": { "$ref": "#/definitions/route" } + }, + "privateRegistry": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "type": { "type": "string", "enum": ["", "nexus", "artifactory"] }, + "apiUrl": { "type": "string" }, + "apiKey": { "type": "string" }, + "username": { "type": "string" }, + "password": { "type": "string" }, + "interval": { "type": "string" }, + "includePattern": { "type": "string" }, + "excludePattern": { "type": "string" }, + "ignoreSslErrors": { "type": "boolean" }, + "supportedEcosystemsOnly": { "type": "boolean" }, + "includeVirtual": { "type": "boolean" } + } + } + } + }, + "registries": { + "type": "object", + "additionalProperties": false, + "properties": { + "npm": { "$ref": "#/definitions/registryEntry" }, + "pypi": { "$ref": "#/definitions/registryEntry" }, + "maven": { "$ref": "#/definitions/registryEntry" }, + "rubygems": { "$ref": "#/definitions/registryEntry" }, + "cargo": { "$ref": "#/definitions/registryEntry" }, + "openvsx": { "$ref": "#/definitions/registryEntry" }, + "nuget": { "$ref": "#/definitions/registryEntry" }, + "go": { "$ref": "#/definitions/registryEntry" }, + "conda": { "$ref": "#/definitions/registryEntry" }, + "huggingface": { "$ref": "#/definitions/registryEntry" } + } + }, + "dnsRouting": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "registries": { + "type": "array", + "items": { "$ref": "#/definitions/ecosystem" } + } + } + }, + "proxy": { + "type": "object", + "additionalProperties": false, + "properties": { + "bufferSize": { "type": "string" }, + "buffersCount": { "type": "integer" }, + "buffersSize": { "type": "string" }, + "busyBuffersSize": { "type": "string" }, + "connectTimeout": { "type": "integer" }, + "sendTimeout": { "type": "integer" }, + "readTimeout": { "type": "integer" }, + "stripRequestHeaders": { "$ref": "#/definitions/stringArray" } + } + }, + "nginx": { + "type": "object", + "additionalProperties": false, + "properties": { + "workerProcesses": { "$ref": "#/definitions/portLike" }, + "workerConnections": { "type": "integer" }, + "workerRlimitNofile": { "type": "integer" }, + "keepalivePoolSize": { "type": "integer" }, + "typesHashMaxSize": { "type": "integer" }, + "clientMaxBodySize": { "type": "string" }, + "acceptMutex": { "type": ["string", "boolean"] }, + "http2": { "type": "boolean" }, + "resolver": { "type": "string" }, + "resolverTimeout": { "type": "string" }, + "accessLogBuffer": { "type": "string" }, + "gzip": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": ["string", "boolean"] }, + "minLength": { "type": "integer" } + } + }, + "openFileCache": { + "type": "object", + "additionalProperties": false, + "properties": { + "max": { "type": "integer" }, + "inactive": { "type": "string" } + } + }, + "proxyCache": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": ["string", "boolean"] }, + "path": { "type": "string" }, + "size": { "type": "string" }, + "inactive": { "type": "string" } + } + } + } + }, + "ports": { + "type": "object", + "additionalProperties": false, + "properties": { + "http": { "$ref": "#/definitions/nullablePort" }, + "https": { "$ref": "#/definitions/nullablePort" }, + "disableHttp": { "type": "boolean" }, + "disableHttps": { "type": "boolean" } + } + }, + "ssl": { + "type": "object", + "additionalProperties": false, + "properties": { + "cert": { "type": "string" }, + "key": { "type": "string" }, + "caCert": { "type": "string" } + } + }, + "lua": { + "type": "object", + "additionalProperties": false, + "properties": { + "cacheSize": { "type": "string" }, + "statsSize": { "type": "string" } + } + }, + "clientIp": { + "type": "object", + "additionalProperties": false, + "properties": { + "header": { "type": "string" }, + "trustedProxies": { "$ref": "#/definitions/stringArray" }, + "recursive": { "type": "boolean" } + } + }, + "forwardProxy": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "port": { "type": "integer" }, + "maxTunnelLifetimeSeconds": { "type": "integer" }, + "maxConnectionsPerSource": { "type": "integer" }, + "proxyProtocolPort": { "type": "integer" }, + "skipStreamLuaCheck": { "type": "boolean" }, + "service": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] }, + "annotations": { "$ref": "#/definitions/freeformObject" }, + "nodePort": { "$ref": "#/definitions/portLike" }, + "externalTrafficPolicy": { "type": "string", "enum": ["", "Cluster", "Local"] }, + "loadBalancerSourceRanges": { "$ref": "#/definitions/stringArray" } + } + } + } + }, + "metadataFiltering": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "filterBlocked": { "type": "boolean" }, + "filterWarn": { "type": "boolean" }, + "includeUncheckedVersions": { "type": "boolean" }, + "maxVersions": { "type": "integer" }, + "cacheTtl": { "type": "integer" }, + "batchSize": { "type": "integer" }, + "maxBodySize": { "type": "string" }, + "excludedEcosystems": { "$ref": "#/definitions/stringArray" }, + "prefetchEnabled": { "type": "boolean" }, + "prefetchTtl": { "type": "integer" }, + "prefetchMaxConcurrent": { "type": "integer" }, + "prefetchBatchConcurrency": { "type": "integer" }, + "metadataConcurrentBatch": { "type": "integer" }, + "condaPrefetchArchs": { "$ref": "#/definitions/stringArray" }, + "maxConcurrent": { "type": "integer" }, + "packageFilterTimeout": { "type": "integer" }, + "semaphoreWaitTimeout": { "type": "integer" }, + "packageFilterRetry": { "type": "integer" }, + "responseCacheEnabled": { "type": "boolean" }, + "responseCacheTtl": { "type": "integer" }, + "responseCacheFresh": { "type": "integer" }, + "cacheAuthenticated": { "type": "boolean" }, + "responseCacheExcludedEcosystems": { "$ref": "#/definitions/stringArray" }, + "confirmAllowMode": { "$ref": "#/definitions/confirmAllowMode" } + } + }, + "redis": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "host": { "type": "string" }, + "port": { "type": "integer" }, + "password": { "type": "string" }, + "existingSecret": { "type": "string" }, + "existingSecretKey": { "type": "string" }, + "db": { "type": "integer" }, + "timeout": { "type": "integer" }, + "poolSize": { "type": "integer" }, + "ttl": { "type": "integer" }, + "ssl": { "type": "boolean" }, + "sslVerify": { "type": "boolean" }, + "sslServerName": { "type": "string" }, + "sslCaCert": { "type": "string" }, + "sslCaCertExistingSecret": { "type": "string" }, + "sslCaCertExistingSecretKey": { "type": "string" }, + "sslClientCert": { "type": "string" }, + "sslClientCertExistingSecret": { "type": "string" }, + "sslClientCertExistingSecretKey": { "type": "string" }, + "sslClientKey": { "type": "string" }, + "sslClientKeyExistingSecret": { "type": "string" }, + "sslClientKeyExistingSecretKey": { "type": "string" } + } + }, + "splunk": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "hecUrl": { "type": "string" }, + "hecToken": { "type": "string" }, + "existingSecret": { "type": "string" }, + "existingSecretKey": { "type": "string" }, + "index": { "type": "string" }, + "source": { "type": "string" }, + "sourcetype": { "type": "string" }, + "sslVerify": { "type": "boolean" }, + "timeout": { "type": "integer" }, + "batchSize": { "type": "integer" } + } + }, + "webhook": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "url": { "type": "string" }, + "authHeader": { "type": "string" }, + "sslVerify": { "type": "boolean" }, + "timeout": { "type": "integer" }, + "onBlock": { "type": "boolean" }, + "onWarn": { "type": "boolean" }, + "onMonitor": { "type": "boolean" }, + "onIgnore": { "type": "boolean" }, + "batchEnabled": { "type": "boolean" }, + "batchSize": { "type": "integer" }, + "batchPeriod": { "type": "integer" } + } + }, + "externalRegistryCooldown": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "enablePublicQuery": { "type": "boolean" }, + "cooldownPeriod": { "type": "string" }, + "checkInterval": { "type": "string" }, + "redisKeyPrefix": { "type": "string" }, + "cacheTtl": { "type": "integer" }, + "mode": { "type": "string", "enum": ["", "api", "local"] }, + "fallback": { "type": "string", "enum": ["", "external", "artifactory", "nexus"] }, + "registries": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "url", "ecosystem"], + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "ecosystem": { "type": "string" }, + "authType": { "type": "string", "enum": ["none", "basic", "bearer"] }, + "authCredential": { "type": "string" }, + "rateLimitHeader": { "type": "string" }, + "ignoreSslErrors": { "type": "boolean" }, + "timeout": { "type": "integer" }, + "cooldownPeriod": { "type": "string" } + } + } + }, + "privateRegistry": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "authType": { "type": "string", "enum": ["bearer_token", "basic"] }, + "authCredential": { "type": "string" }, + "source": { "type": "string", "enum": ["auto", "artifactory", "nexus"] }, + "includeUnsupportedOnly": { "type": "boolean" }, + "includePattern": { "type": "string" }, + "excludePattern": { "type": "string" }, + "cooldownPeriod": { "type": "string" } + } + } + } + }, + "extraConfig": { "$ref": "#/definitions/freeformObject" }, + "metrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "minImageVersion": { "type": "string" }, + "port": { "type": "integer" }, + "podAnnotations": { "type": "boolean" }, + "serviceMonitor": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "namespace": { "type": "string" }, + "interval": { "type": "string" }, + "scrapeTimeout": { "type": "string" }, + "labels": { "$ref": "#/definitions/freeformObject" }, + "honorLabels": { "type": "boolean" }, + "relabelings": { "$ref": "#/definitions/objectArray" }, + "metricRelabelings": { "$ref": "#/definitions/objectArray" } + } + } + } + }, + "service": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] }, + "httpPort": { "type": "integer" }, + "httpsPort": { "type": "integer" }, + "containerHttpPort": { "$ref": "#/definitions/portLike" }, + "containerHttpsPort": { "$ref": "#/definitions/portLike" }, + "httpsTargetPort": { "$ref": "#/definitions/portLike" }, + "externalTrafficPolicy": { "type": "string", "enum": ["", "Cluster", "Local"] }, + "annotations": { "$ref": "#/definitions/freeformObject" } + } + }, + "ingress": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "className": { "type": "string" }, + "annotations": { "$ref": "#/definitions/freeformObject" }, + "hosts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { "type": "string" }, + "paths": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { "type": "string" }, + "pathType": { "type": "string" } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "secretName": { "type": "string" }, + "hosts": { "$ref": "#/definitions/stringArray" } + } + } + } + } + }, + "tls": { + "type": "object", + "additionalProperties": false, + "properties": { + "generateSelfSigned": { "type": "boolean" }, + "existingSecret": { "type": "string" }, + "certManager": { "type": "boolean" }, + "includeCaCrt": { "type": "boolean" }, + "certificate": { "type": "string" }, + "privateKey": { "type": "string" } + } + }, + "resources": { "$ref": "#/definitions/freeformObject" }, + "healthCheck": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "path": { "type": "string" }, + "initialDelaySeconds": { "type": "integer" }, + "periodSeconds": { "type": "integer" }, + "timeoutSeconds": { "type": "integer" }, + "failureThreshold": { "type": "integer" } + } + }, + "terminationGracePeriodSeconds": { "$ref": "#/definitions/portLike" }, + "podAnnotations": { "$ref": "#/definitions/freeformObject" }, + "podSecurityContext": { "$ref": "#/definitions/freeformObject" }, + "securityContext": { "$ref": "#/definitions/freeformObject" }, + "nodeSelector": { "$ref": "#/definitions/freeformObject" }, + "tolerations": { "$ref": "#/definitions/objectArray" }, + "affinity": { "$ref": "#/definitions/freeformObject" }, + "topologySpreadConstraints": { "$ref": "#/definitions/objectArray" }, + "extraContainers": { "$ref": "#/definitions/objectArray" }, + "initContainers": { + "type": "object", + "additionalProperties": false, + "properties": { + "copyApp": { + "type": "object", + "additionalProperties": false, + "properties": { + "securityContext": { "$ref": "#/definitions/containerSecurityContext" } + } + }, + "certGenerator": { + "type": "object", + "additionalProperties": false, + "properties": { + "image": { + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { "type": "string" }, + "tag": { "type": "string" }, + "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } + } + }, + "securityContext": { "$ref": "#/definitions/containerSecurityContext" } + } + } + } + }, + "serviceAccount": { + "type": "object", + "additionalProperties": false, + "properties": { + "create": { "type": "boolean" }, + "name": { "type": "string" }, + "annotations": { "$ref": "#/definitions/freeformObject" } + } + } + } +}