From caa37338808eabec477d6f230b1b8993543f01f4 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Fri, 23 Sep 2022 01:05:40 +0300 Subject: [PATCH] feat(blocky): add blocky (#3735) * feat(blocky): add blocky * Chore(Blocky): Ornias's refactor * Chore(Blocky): stavros' refactor * add basic run tests and remaining config options in values.yaml * bump common again * correct minor services whoopsy * whoops again * Update charts/incubator/blocky/questions.yaml Signed-off-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> * Update charts/incubator/blocky/questions.yaml Signed-off-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> * Actually add values.yaml settings to blockyconfig file * hmm * Update charts/incubator/blocky/templates/common.yaml Co-authored-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Signed-off-by: Kjeld Schouten-Lebbing * dont load k8s-gateway without domains added * remove loop detection from k8s-gateway * response with nxdomain if no forwarding is added to k8s-gateway and fix k8s-gateway domains in blocky config * hmmm * fix some mistakes * fix config mistake * always add a forward to prevent errors, even though forwarding would never be used. Signed-off-by: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Signed-off-by: Kjeld Schouten-Lebbing Co-authored-by: Kjeld Schouten-Lebbing --- charts/incubator/blocky/.helmignore | 30 + charts/incubator/blocky/Chart.yaml | 33 + charts/incubator/blocky/README.md | 0 .../blocky/ci/k8sgateway-values.yaml | 7 + .../blocky/ci/standalone-values.yaml | 0 .../blocky/docs/installation-notes.md | 87 +++ charts/incubator/blocky/icon.png | Bin 0 -> 40200 bytes charts/incubator/blocky/questions.yaml | 597 ++++++++++++++++++ .../blocky/templates/_blockyConfig.tpl | 200 ++++++ .../blocky/templates/_k8sgateway.tpl | 107 ++++ charts/incubator/blocky/templates/_webui.tpl | 36 ++ charts/incubator/blocky/templates/common.yaml | 50 ++ charts/incubator/blocky/values.yaml | 338 ++++++++++ cspell.config.yaml | 13 + 14 files changed, 1498 insertions(+) create mode 100644 charts/incubator/blocky/.helmignore create mode 100644 charts/incubator/blocky/Chart.yaml create mode 100644 charts/incubator/blocky/README.md create mode 100644 charts/incubator/blocky/ci/k8sgateway-values.yaml create mode 100644 charts/incubator/blocky/ci/standalone-values.yaml create mode 100644 charts/incubator/blocky/docs/installation-notes.md create mode 100644 charts/incubator/blocky/icon.png create mode 100644 charts/incubator/blocky/questions.yaml create mode 100644 charts/incubator/blocky/templates/_blockyConfig.tpl create mode 100644 charts/incubator/blocky/templates/_k8sgateway.tpl create mode 100644 charts/incubator/blocky/templates/_webui.tpl create mode 100644 charts/incubator/blocky/templates/common.yaml create mode 100644 charts/incubator/blocky/values.yaml diff --git a/charts/incubator/blocky/.helmignore b/charts/incubator/blocky/.helmignore new file mode 100644 index 00000000000..77ca5567b26 --- /dev/null +++ b/charts/incubator/blocky/.helmignore @@ -0,0 +1,30 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# OWNERS file for Kubernetes +OWNERS +# helm-docs templates +*.gotmpl +# docs folder +/docs +# icon +icon.png diff --git a/charts/incubator/blocky/Chart.yaml b/charts/incubator/blocky/Chart.yaml new file mode 100644 index 00000000000..40d68c6c4db --- /dev/null +++ b/charts/incubator/blocky/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +appVersion: "10.6.2" +dependencies: + - name: common + repository: https://library-charts.truecharts.org + version: 10.5.7 + - condition: redis.enabled + name: redis + repository: https://charts.truecharts.org + version: 3.0.67 +description: Blocky is a DNS proxy and ad-blocker for the local network written in Go +home: https://truecharts.org/docs/charts/stable/airsonic +icon: https://truecharts.org/img/hotlink-ok/chart-icons/airsonic.png +keywords: + - dns + - blocky +kubeVersion: ">=1.16.0-0" +maintainers: + - email: info@truecharts.org + name: TrueCharts + url: https://truecharts.org +name: blocky +sources: + - https://0xerr0r.github.io/blocky/ + - https://github.com/0xERR0R/blocky + - https://github.com/Mozart409/blocky-frontend + - https://hub.docker.com/r/spx01/blocky +version: 0.0.1 +annotations: + truecharts.org/catagories: | + - network + truecharts.org/SCALE-support: "true" + truecharts.org/grade: U diff --git a/charts/incubator/blocky/README.md b/charts/incubator/blocky/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/charts/incubator/blocky/ci/k8sgateway-values.yaml b/charts/incubator/blocky/ci/k8sgateway-values.yaml new file mode 100644 index 00000000000..4bddcbf7f12 --- /dev/null +++ b/charts/incubator/blocky/ci/k8sgateway-values.yaml @@ -0,0 +1,7 @@ +k8sgateway: + enabled: true + # -- list of processed domains + domains: + - domain: something.com + dnsChallenge: + enabled: false diff --git a/charts/incubator/blocky/ci/standalone-values.yaml b/charts/incubator/blocky/ci/standalone-values.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/charts/incubator/blocky/docs/installation-notes.md b/charts/incubator/blocky/docs/installation-notes.md new file mode 100644 index 00000000000..4ad649347aa --- /dev/null +++ b/charts/incubator/blocky/docs/installation-notes.md @@ -0,0 +1,87 @@ +# Installation notes + +## Default Configuration + +The following config will be pre-configured and merged with any config you manually add to `blockyConfig` option in `values.yaml`: + +Redis (always present): + +```yaml +redis: + address: $redis_host:6379 + password: $redis_pass + database: 0 + required: true + connectionAttempts: 10 + connectionCooldown: 3s +``` + +Prometheus (Only present if enabled): + +```yaml +prometheus: + enable: true + path: /metrics +``` + +Upstreams (from values.yaml): + +```yaml +upstream: + default: + - # Content from `.Values.defaultUpstreams` + # Additional upstream groups from `.Values.upstreams` +``` + +Whitelist/Blacklist (from values.yaml) : + +```yaml +blocking: + blockType: nxDomain + blockTTL: 6h + refreshPeriod: 4h + downloadTimeout: 60s + downloadAttempts: 3 + downloadCooldown: 2s + failStartOnListError: false + processingConcurrency: 4 + whiteLists: + # Groupname: + - # Content from .Values.blocking.whiteList + blackLists: + # Groupname: + - # Content from .Values.blocking.blackList + clientGroupsBlock: + # Groupname: + - # Content from .Values.blocking.clientGroupsBlock +``` + +## Configuration Instructions + +### TrueNAS SCALE + +For TrueNAS SCALE, we offer only a limited subset of configuration options: + +- Upstream DNS servers +- Whitelists +- Blacklists + +Those have special variables in `values.yaml`, so we can show them nicely in the TrueNAS SCALE GUI + +### Native Helm + +For anything but TrueNAS SCALE, we would advice to instead use `blockyConfig` in `Values.yaml` and NOT mount any configuration file manually. + +In short: + +- Add your config in `values.yaml` under `blockyConfig:` +- Add your whitelists in `values.yaml` under `blockyWhitelist` or manually using blockyConfig +- Add your blacklists in `values.yaml` under `blockyBlacklist` or manually using blockyConfig + +### Adding config by mounting files + +You can mount custom config files, using `persistence` or, in SCALE GUI, `Additional Storage` to the following path: +`/app/config/` +_However it cannot reference any of the pre-defined variables listed above, so it's use is severely limited._ + +You can also mount custom Whitelist/Blacklist files, using `persistence` or, in SCALE GUI, `Additional Storage` and enter the path in your whitelist or blacklist settings manually diff --git a/charts/incubator/blocky/icon.png b/charts/incubator/blocky/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e4e506d78ef7e8cd5a978969a423b1b7889129da GIT binary patch literal 40200 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 Iyd~`a0P7-7YybcN literal 0 HcmV?d00001 diff --git a/charts/incubator/blocky/questions.yaml b/charts/incubator/blocky/questions.yaml new file mode 100644 index 00000000000..9ec970e875a --- /dev/null +++ b/charts/incubator/blocky/questions.yaml @@ -0,0 +1,597 @@ +# Include{groups} +portals: + open: +# Include{portalLink} +questions: +# Include{global} +# Include{controller} +# Include{controllerDeployment} +# Include{replicas} +# Include{replica1} +# Include{strategy} +# Include{recreate} +# Include{controllerExpert} +# Include{controllerExpertExtraArgs} + - variable: blocky + group: Container Configuration + label: Blocky Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: enableWebUI + label: Enable Web UI + description: Enables Web UI + schema: + type: boolean + default: true + - variable: enablePrometheus + label: Enable Prometheus Endpoint + description: Enables Prometheus Endpoint + schema: + type: boolean + default: true + - variable: overrideDefaults + group: Container Configuration + label: Override Default Upstreams + description: Overrides the predefined DNS server upstream list + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: defaultUpstreams + label: Default Upstreams + schema: + type: list + default: [] + items: + - variable: upstreamEntry + label: Upstream Entry + schema: + type: string + required: true + default: "" + - variable: upstreams + group: Container Configuration + label: Upstreams Groups + description: + schema: + type: list + default: [] + items: + - variable: upstreamsGroupEntry + label: Upstreams Group Entry + schema: + additional_attrs: true + type: dict + attrs: + - variable: name + label: Group Name + schema: + type: string + required: true + default: "" + - variable: upstreams + label: Upstreams + schema: + type: list + required: true + default: [] + items: + - variable: upstreamEntry + label: upstream Entry + schema: + type: string + required: true + default: "" + - variable: conditional + group: Container Configuration + label: Conditional + schema: + additional_attrs: true + type: dict + attrs: + - variable: rewrite + label: Rewrite + schema: + type: list + default: [] + items: + - variable: rewriteEntry + label: Rewrite Entry + schema: + type: dict + additional_attrs: true + attrs: + - variable: in + label: In + schema: + type: string + required: true + default: "" + - variable: out + label: Out + schema: + type: string + required: true + default: "" + - variable: mapping + label: Mapping + schema: + type: list + default: [] + items: + - variable: mappingEntry + label: Mapping Entry + schema: + type: dict + additional_attrs: true + attrs: + - variable: domain + label: Domain + schema: + type: string + required: true + default: "" + - variable: dnsserver + label: DNS Server + schema: + type: string + required: true + default: "" + - variable: blocking + group: Container Configuration + label: Blocking + schema: + additional_attrs: true + type: dict + attrs: + - variable: blockType + label: Block Type + description: Set the response should be sent to the client, if a requested query is blocked + schema: + type: string + default: nxDomain + - variable: blockTTL + label: Block TTL + description: Set the TTL for answers to blocked domains + schema: + type: string + default: 6h + - variable: refreshPeriod + label: Refresh Period + description: Set how often blocky should refresh list cache + schema: + type: string + default: 4h + - variable: downloadTimeout + label: Download Timeout + description: Download attempt timeout + schema: + type: string + default: 60s + - variable: downloadAttempts + label: Download Attempts + description: How many download attempts should be performed + schema: + type: int + default: 3 + - variable: downloadCooldown + label: Download Cooldown + description: Time between the download attempts + schema: + type: string + default: 2s + - variable: failStartOnListError + label: Fail Start on List Error + description: Fail to start if at least one list can't be downloaded or opened + schema: + type: boolean + default: false + - variable: processingConcurrency + label: Processing Concurrency + description: Sets how many list-groups can be processed at the same time + schema: + type: int + default: 4 + - variable: whitelist + label: Whitelist + description: Define whitelists, either URL or file + schema: + type: list + default: [] + items: + - variable: whitelistEntry + label: Whitelist Group Entry + schema: + additional_attrs: true + type: dict + attrs: + - variable: name + label: Group Name + schema: + type: string + required: true + default: "" + - variable: lists + label: Lists + schema: + type: list + required: true + default: [] + items: + - variable: listEntry + label: List Entry + schema: + type: string + required: true + default: "" + - variable: blacklist + label: Blacklist + description: Define blacklists, either URL or file + schema: + type: list + default: [] + items: + - variable: blacklistEntry + label: Blacklist Group Entry + schema: + additional_attrs: true + type: dict + attrs: + - variable: name + label: Group Name + schema: + type: string + required: true + default: "" + - variable: lists + label: Lists + schema: + type: list + required: true + default: [] + items: + - variable: listEntry + label: List Entry + schema: + type: string + required: true + default: "" + - variable: clientGroupsBlock + label: Client Groups Block + description: Define, which blocking group(s) should be used for which client in your network. + schema: + type: list + default: [] + items: + - variable: clientGroupBlockEntry + label: Client Group Block Entry + schema: + additional_attrs: true + type: dict + attrs: + - variable: name + label: Client Group Name + schema: + type: string + required: true + default: "" + - variable: groups + label: Groups + schema: + type: list + required: true + default: [] + items: + - variable: groupEntry + label: Group Entry + schema: + type: string + required: true + default: "" + - variable: k8sgateway + group: Container Configuration + label: k8s-Gateway Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: enabled + label: Enable k8s-Gateway + description: Enables k8s-Gateway + schema: + type: boolean + default: true + show_subquestions_if: true + subquestions: + - variable: domains + label: Domains + description: Please refer to CoreDNS docs for options + schema: + type: list + default: [] + items: + - variable: domainEntry + label: "" + schema: + additional_attrs: true + type: dict + attrs: + - variable: domain + label: Domain name + schema: + type: string + required: true + default: example.com + - variable: dnsChallenge + label: Forward dnsChallenge + description: Optional configuration option for DNS01 challenge that will redirect all acme + schema: + additional_attrs: true + type: dict + attrs: + - variable: enabled + label: Enable + schema: + type: boolean + default: false + show_subquestions_if: true + subquestions: + - variable: domain + label: Forward to Domain + schema: + type: string + required: true + default: dns01.clouddns.com + + - variable: advancedOptions + label: Advanced Options + schema: + type: boolean + default: false + show_if: [["enabled", "=", "true"]] + show_subquestions_if: true + subquestions: + - variable: ttl + label: ttl + description: TTL for non-apex responses (in seconds) + schema: + type: int + default: 300 + - variable: watchedResources + label: Watched Resources + description: imit what kind of resources to watch, e.g. Ingress + schema: + type: list + default: [] + items: + - variable: watchedResource + label: Watched Resource + schema: + type: string + default: "" + - variable: secondary + label: Secondary DNS Server Service + description: Service name of a secondary DNS server (should be serviceName.namespace) + schema: + type: string + default: "" + - variable: apex + label: Apex + description: Override the default `serviceName.namespace` domain apex + schema: + type: string + default: "" +# Include{containerConfig} +# Include{serviceRoot} + - variable: main + label: Main Service + description: The Primary service on which the healthcheck runs, often the webUI + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelectorLoadBalancer} +# Include{serviceSelectorExtras} + - variable: main + label: Main Service Port Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: Port + description: This port exposes the container port on the service + schema: + type: int + default: 10315 + required: true +# Include{advancedPortHTTP} + - variable: targetPort + label: Target Port + description: The internal(!) port on the container the Application runs on + schema: + type: int + default: 80 + - variable: dns-tcp + label: DNS TCP Service + description: The DNS TCP service + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelectorLoadBalancer} +# Include{serviceSelectorExtras} + - variable: dns-tcp + label: DNS TCP Port Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: Port + description: This port exposes the container port on the service + schema: + type: int + default: 53 + required: true +# Include{advancedPortTCP} + - variable: targetPort + label: Target Port + description: The internal(!) port on the container the Application runs on + schema: + type: int + default: 53 + - variable: dns-udp + label: DNS UDP Service + description: The DNS UDP service + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelectorLoadBalancer} +# Include{serviceSelectorExtras} + - variable: dns-udp + label: DNS UDP Port Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: Port + description: This port exposes the container port on the service + schema: + type: int + default: 53 + required: true +# Include{advancedPortUDP} + - variable: targetPort + label: Target Port + description: The internal(!) port on the container the Application runs on + schema: + type: int + default: 53 + - variable: dot + label: DoT Service + description: "DNS-over-TLS service" + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelectorClusterIP} +# Include{serviceSelectorExtras} + - variable: dot + label: DoT Port Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: Port + description: This port exposes the container port on the service + schema: + type: int + default: 853 + required: true +# Include{advancedPortUDP} + - variable: targetPort + label: Target Port + description: The internal(!) port on the container the Application runs on + schema: + type: int + default: 853 + - variable: http + label: HTTP and Metrics Service + description: "service for things like metrics, pprof, API, DoH etc" + schema: + additional_attrs: true + type: dict + attrs: +# Include{serviceSelectorClusterIP} +# Include{serviceSelectorExtras} + - variable: http + label: HTTP and Metrics Port Configuration + schema: + additional_attrs: true + type: dict + attrs: + - variable: port + label: Port + description: This port exposes the container port on the service + schema: + type: int + default: 4000 + required: true +# Include{advancedPortUDP} + - variable: targetPort + label: Target Port + description: The internal(!) port on the container the Application runs on + schema: + type: int + default: 4000 +# Include{serviceExpertRoot} + default: false +# Include{serviceExpert} +# Include{serviceList} +# Include{persistenceList} +# Include{ingressRoot} + - variable: main + label: Main Ingress + schema: + additional_attrs: true + type: dict + attrs: +# Include{ingressDefault} +# Include{ingressTLS} +# Include{ingressTraefik} +# Include{ingressExpert} +# Include{ingressList} +# Include{security} +# Include{securityContextAdvancedRoot} + - variable: privileged + label: Privileged mode + schema: + type: boolean + default: false + - variable: readOnlyRootFilesystem + label: ReadOnly Root Filesystem + schema: + type: boolean + default: true + - variable: allowPrivilegeEscalation + label: Allow Privilege Escalation + schema: + type: boolean + default: false + - variable: runAsNonRoot + label: runAsNonRoot + schema: + type: boolean + default: true +# Include{securityContextAdvanced} +# Include{podSecurityContextRoot} + - variable: runAsUser + label: runAsUser + description: The UserID of the user running the application + schema: + type: int + default: 568 + - variable: runAsGroup + label: runAsGroup + description: The groupID this App of the user running the application + schema: + type: int + default: 568 + - variable: fsGroup + label: fsGroup + description: The group that should own ALL storage. + schema: + type: int + default: 568 +# Include{podSecurityContextAdvanced} +# Include{resources} +# Include{advanced} +# Include{addons} +# Include{documentation} diff --git a/charts/incubator/blocky/templates/_blockyConfig.tpl b/charts/incubator/blocky/templates/_blockyConfig.tpl new file mode 100644 index 00000000000..1246345de5b --- /dev/null +++ b/charts/incubator/blocky/templates/_blockyConfig.tpl @@ -0,0 +1,200 @@ +{{/* Define the config */}} +{{- define "blocky.configmap" -}} +{{- $configName := printf "%s-config" (include "tc.common.names.fullname" .) }} +{{- $config := merge ( include "blocky.config" . | fromYaml ) ( .Values.blockyConfig ) }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $configName }} + labels: + {{- include "tc.common.labels" . | nindent 4 }} +data: + tc-config.yaml: | +{{ $config | toYaml | indent 6 }} +{{- end -}} + +{{- define "blocky.config" -}} +redis: + address: {{ printf "%v-%v" .Release.Name "redis" }}:6379 + password: {{ .Values.redis.redisPassword | trimAll "\"" }} + database: 0 + required: true + connectionAttempts: 10 + connectionCooldown: 3s +{{- if .Values.blocky.enablePrometheus }} +prometheus: + enable: true + path: /metrics +{{- end }} +upstream: + default: +{{- .Values.defaultUpstreams | toYaml | nindent 8 }} + +{{- if .Values.certFile }} +certFile: {{ .Values.certFile }} +{{- end }} + +{{- if .Values.keyFile }} +keyFile: {{ .Values.keyFile }} +{{- end }} + +{{- if .Values.logLevel }} +logLevel: {{ .Values.logLevel }} +{{- end }} + +{{- if .Values.logTimestamp }} +logTimestamp: {{ .Values.logTimestamp }} +{{- end }} + +{{- if .Values.logPrivacy }} +logPrivacy: {{ .Values.logPrivacy }} +{{- end }} + +{{- if .Values.dohUserAgent }} +dohUserAgent: {{ .Values.dohUserAgent }} +{{- end }} + +{{- if .Values.minTlsServeVersion }} +minTlsServeVersion: {{ .Values.minTlsServeVersion }} +{{- end }} + +caching: +{{ toYaml .Values.caching | indent 2 }} + + +{{- if .Values.hostsFile.enabled }} +{{ $hostsfile := omit .Values.hostsFile "enabled" }} +hostsFile: +{{ toYaml $hostsfile | indent 2 }} +{{- end }} + +{{- range $id, $value := .Values.upstreams }} + {{ $value.name }}: +{{- $value.dnsservers | toYaml | nindent 8 }} +{{- end }} + +{{- if or .Values.bootstrapDns.upstream .Values.bootstrapDns.ips }} +bootstrapDns: +{{- if .Values.bootstrapDns.upstream }} + upstream: {{ .Values.bootstrapDns.upstream }} +{{- end }} +{{- if .Values.bootstrapDns.ips }} + ips: +{{- range $id, $value := .Values.bootstrapDns.ips }} + - {{ $value }} +{{- end }} +{{- end }} +{{- end }} + +{{- if or .Values.filtering.filtering }} +filtering: +{{- if .Values.filtering.ips }} + queryTypes: +{{- range $id, $value := .Values.filtering.ips }} + - {{ $value }} +{{- end }} +{{- end }} +{{- end }} + +{{- if or .Values.customDNS.filterUnmappedTypes .Values.customDNS.customTTL .Values.customDNS.rewrite .Values.customDNS.mapping }} +customDNS: +{{- if .Values.customDNS.upstream }} + upstream: {{ .Values.customDNS.upstream }} +{{- end }} +{{- if .Values.customDNS.customTTL }} + customTTL: {{ .Values.customDNS.customTTL }} +{{- end }} +{{- if .Values.customDNS.rewrite }} + rewrite: +{{- range $id, $value := .Values.customDNS.rewrite }} + {{ $value.in }}: {{ $value.out }} +{{- end }} +{{- end }} + +{{- if .Values.customDNS.mapping }} + mapping: +{{- range $id, $value := .Values.customDNS.mapping }} + {{ $value.domain }}: {{ $value.dnsserver }} +{{- end }} +{{- end }} +{{- end }} + +{{- if or .Values.clientLookup.upstream .Values.clientLookup.ips }} +clientLookup: +{{- if .Values.clientLookup.upstream }} + upstream: {{ .Values.clientLookup.upstream }} +{{- end }} +{{- if .Values.clientLookup.ips }} + singleNameOrder: +{{- range $id, $value := .Values.clientLookup.ips }} + - {{ $value }} +{{- end }} +{{- end }} +{{- if .Values.clientLookup.clients }} + clients: +{{- range $id, $value := .Values.clientLookup.clients }} + {{ $value.domain }}: + {{- range $id, $value := .ips }} + - {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} + +{{- if or .Values.conditional.rewrite .Values.conditional.mapping ( and .Values.k8sgateway.enabled .Values.k8sgateway.domains ) }} +conditional: +{{- if .Values.conditional.rewrite }} + rewrite: +{{- range $id, $value := .Values.conditional.rewrite }} + {{ $value.in }}: {{ $value.out }} +{{- end }} +{{- end }} + +{{- if or .Values.conditional.mapping ( and .Values.k8sgateway.enabled .Values.k8sgateway.domains ) }} + mapping: +{{- if and .Values.k8sgateway.enabled .Values.k8sgateway.domains }} +{{- range $id, $value := .Values.k8sgateway.domains }} + {{ .domain }}: 127.0.0.1:{{ $.Values.service.k8sgateway.ports.k8sgateway.targetPort }} +{{- end }} +{{- end }} +{{- range $id, $value := .Values.conditional.mapping }} + {{ $value.domain }}: {{ $value.dnsserver }} +{{- end }} +{{- end }} +{{- end }} + +blocking: + blockType: {{ .Values.blocking.blockType }} + blockTTL: {{ .Values.blocking.blockTTL }} + refreshPeriod: {{ .Values.blocking.refreshPeriod }} + downloadTimeout: {{ .Values.blocking.downloadTimeout }} + downloadAttempts: {{ .Values.blocking.downloadAttempts }} + downloadCooldown: {{ .Values.blocking.downloadCooldown }} + failStartOnListError: {{ .Values.blocking.failStartOnListError }} + processingConcurrency: {{ .Values.blocking.processingConcurrency }} +{{- if .Values.blocking.whitelist }} + whiteLists: +{{- range $id, $value := .Values.blocking.whitelist }} + {{ $value.name }}: +{{- $value.lists | toYaml | nindent 10 }} +{{- end }} +{{- end }} + +{{- if .Values.blocking.blacklist }} + blackLists: +{{- range $id, $value := .Values.blocking.blacklist }} + {{ $value.name }}: +{{- $value.lists | toYaml | nindent 10 }} +{{- end }} +{{- end }} + +{{- if .Values.blocking.clientGroupsBlock }} + clientGroupsBlock: +{{- range $id, $value := .Values.blocking.clientGroupsBlock }} + {{ $value.name }}: +{{- $value.groups | toYaml | nindent 10 }} +{{- end }} +{{- end }} + +{{- end -}} diff --git a/charts/incubator/blocky/templates/_k8sgateway.tpl b/charts/incubator/blocky/templates/_k8sgateway.tpl new file mode 100644 index 00000000000..94cccf78438 --- /dev/null +++ b/charts/incubator/blocky/templates/_k8sgateway.tpl @@ -0,0 +1,107 @@ +{{- define "k8sgateway.container" -}} +image: {{ .Values.k8sgatewayImage.repository }}:{{ .Values.k8sgatewayImage.tag }} +imagePullPolicy: {{ .Values.k8sgatewayImage.pullPolicy }} +securityContext: + runAsUser: 0 + runAsGroup: 0 + readOnlyRootFilesystem: true + runAsNonRoot: false +args: ["-conf", "/etc/coredns/Corefile"] +ports: + - containerPort: {{ .Values.service.k8sgateway.ports.k8sgateway.targetPort }} + name: main +volumeMounts: + - name: config-volume + mountPath: /etc/coredns +readinessProbe: + httpGet: + path: /ready + port: 8181 + initialDelaySeconds: {{ .Values.probes.readiness.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.readiness.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.readiness.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.readiness.spec.failureThreshold }} +livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: {{ .Values.probes.liveness.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.liveness.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.liveness.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.liveness.spec.failureThreshold }} +startupProbe: + httpGet: + path: /ready + port: 8181 + initialDelaySeconds: {{ .Values.probes.startup.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.startup.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.startup.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.startup.spec.failureThreshold }} +{{- end -}} + +{{/* +Create the matchable regex from domain +*/}} +{{- define "k8sgateway.configmap.regex" -}} +{{- if .Values.k8sgateway.domain }} +{{- .Values.k8sgateway.domain | replace "." "[.]" -}} +{{- else -}} + {{ "unset" }} +{{- end }} +{{- end -}} + +{{/* Define the configmap */}} +{{- define "k8sgateway.configmap" -}} +{{- $values := .Values.k8sgateway }} +{{- $fqdn := ( include "tc.common.names.fqdn" . ) }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "tc.common.names.fullname" . }}-corefile + labels: + {{- include "tc.common.labels" . | nindent 4 }} +data: + Corefile: |- + .:{{ .Values.service.k8sgateway.ports.k8sgateway.targetPort }} { + errors + log + health { + lameduck 5s + } + ready + {{- range .Values.k8sgateway.domains }} + {{- if .dnsChallenge.enabled }} + template IN ANY {{ required "Delegated domain ('domain') is mandatory " .domain }} { + match "_acme-challenge[.](.*)[.]{{ include "k8sgateway.configmap.regex" . }}" + answer "{{ "{{" }} .Name {{ "}}" }} 5 IN CNAME {{ "{{" }} index .Match 1 {{ "}}" }}.{{ required "DNS01 challenge domain is mandatory " $values.dnsChallenge.domain }}" + fallthrough + } + {{- end }} + k8s_gateway "{{ required "Delegated domain ('domain') is mandatory " .domain }}" { + apex {{ $values.apex | default $fqdn }} + ttl {{ $values.ttl }} + {{- if $values.secondary }} + secondary {{ $values.secondary }} + {{- end }} + {{- if $values.watchedResources }} + resources {{ join " " $values.watchedResources }} + {{- end }} + fallthrough + } + {{- end }} + prometheus 0.0.0.0:9153 + {{- if .Values.k8sgateway.forward.enabled }} + forward . {{ .Values.k8sgateway.forward.primary }} {{ .Values.k8sgateway.forward.secondary }} { + {{- range .Values.k8sgateway.forward.options }} + {{ .name }} {{ .value }} + {{- end }} + } + {{- else }} + forward . 1.1.1.1 + {{- end }} + loop + reload + loadbalance + } +{{- end -}} diff --git a/charts/incubator/blocky/templates/_webui.tpl b/charts/incubator/blocky/templates/_webui.tpl new file mode 100644 index 00000000000..881bee2058b --- /dev/null +++ b/charts/incubator/blocky/templates/_webui.tpl @@ -0,0 +1,36 @@ +{{- define "blocky.frontend" -}} +image: {{ .Values.WebUIImage.repository }}:{{ .Values.WebUIImage.tag }} +imagePullPolicy: {{ .Values.WebUIImage.pullPolicy }} +securityContext: + runAsUser: 568 + runAsGroup: 568 + readOnlyRootFilesystem: true + runAsNonRoot: true +ports: + - containerPort: {{ .Values.service.main.ports.main.targetPort }} + name: main +readinessProbe: + httpGet: + path: / + port: {{ .Values.service.main.ports.main.targetPort }} + initialDelaySeconds: {{ .Values.probes.readiness.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.readiness.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.readiness.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.readiness.spec.failureThreshold }} +livenessProbe: + httpGet: + path: / + port: {{ .Values.service.main.ports.main.targetPort }} + initialDelaySeconds: {{ .Values.probes.liveness.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.liveness.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.liveness.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.liveness.spec.failureThreshold }} +startupProbe: + httpGet: + path: / + port: {{ .Values.service.main.ports.main.targetPort }} + initialDelaySeconds: {{ .Values.probes.startup.spec.initialDelaySeconds }} + timeoutSeconds: {{ .Values.probes.startup.spec.timeoutSeconds }} + periodSeconds: {{ .Values.probes.startup.spec.periodSeconds }} + failureThreshold: {{ .Values.probes.startup.spec.failureThreshold }} +{{- end -}} diff --git a/charts/incubator/blocky/templates/common.yaml b/charts/incubator/blocky/templates/common.yaml new file mode 100644 index 00000000000..5a906f353a2 --- /dev/null +++ b/charts/incubator/blocky/templates/common.yaml @@ -0,0 +1,50 @@ +{{/* Make sure all variables are set properly */}} +{{- include "tc.common.loader.init" . }} + +{{ include "blocky.configmap" . }} + +{{/* Always mount the configmap, with the basic config, plus the 'blockyConfig' */}} +{{- define "blocky.configmap.mount" -}} +enabled: true +type: custom +mountPath: /app/config/tc-config.yaml +subPath: tc-config.yaml +readOnly: true +volumeSpec: + configMap: + name: '{{ printf "%s-config" (include "tc.common.names.fullname" .) }}' +{{- end -}} + +{{/* Append the general configMap volume to the volumes */}} +{{- define "k8sgateway.configvolume" -}} +enabled: "true" +mountPath: "/etc/coredns" +readOnly: true +type: "custom" +volumeSpec: + configMap: + name: {{ include "tc.common.names.fullname" . }}-corefile + items: + - key: Corefile + path: Corefile +{{- end -}} + + +{{- $_ := set .Values.persistence "tc-config" (include "blocky.configmap.mount" . | fromYaml) -}} + +{{- $_ := set .Values.podAnnotations "prometheus.io/scrape" "true" -}} +{{- $_ := set .Values.podAnnotations "prometheus.io/path" "/metrics" -}} +{{- $_ := set .Values.podAnnotations "prometheus.io/port" (.Values.service.main.ports.main.targetPort | quote) -}} + +{{- if .Values.blocky.enableWebUI -}} +{{- $_ := set .Values.additionalContainers "frontend" (include "blocky.frontend" . | fromYaml) -}} +{{- end -}} + +{{- if and .Values.k8sgateway.enabled .Values.k8sgateway.domains -}} +{{- include "k8sgateway.configmap" . }} +{{- $_ := set .Values.persistence "config-volume" (include "k8sgateway.configvolume" . | fromYaml) -}} +{{- $_ := set .Values.additionalContainers "k8sgateway" (include "k8sgateway.container" . | fromYaml) -}} +{{- end -}} + +{{/* Render the templates */}} +{{ include "tc.common.loader.apply" . }} diff --git a/charts/incubator/blocky/values.yaml b/charts/incubator/blocky/values.yaml new file mode 100644 index 00000000000..35b82c7d08f --- /dev/null +++ b/charts/incubator/blocky/values.yaml @@ -0,0 +1,338 @@ +image: + repository: spx01/blocky + tag: development@sha256:c55e676e89cee31edeee687d70f7ed957b727d61b5611e213809f7a0399fe4ef + # repository: tccr.io/truecharts/blocky + # tag: v0.19@sha256:77a474542f12f480deca33ff0a6375846918b86988c13f858620839d8818ca84 + pullPolicy: IfNotPresent + +WebUIImage: + repository: tccr.io/truecharts/blocky-frontend + tag: v0.0.3@sha256:81058f20520dcdb80c9883b6f21b338446fefc333e3ca8bd7d17336a24a5d842 + pullPolicy: IfNotPresent + +k8sgatewayImage: + repository: tccr.io/truecharts/k8s_gateway + pullPolicy: IfNotPresent + tag: 0.3.2@sha256:594fd6990eb2e0af1df7df8ba76cb3ca66232f46c5df5ebf786a45dd19777ae5 + +controller: + # -- Set additional annotations on the deployment/statefulset/daemonset + # -- Number of desired pods + replicas: 2 + # -- Set the controller upgrade strategy + # For Deployments, valid values are Recreate (default) and RollingUpdate. + # For StatefulSets, valid values are OnDelete and RollingUpdate (default). + # DaemonSets ignore this. + strategy: RollingUpdate + +# -- Blocky Config File content +blockyConfig: {} +# upstream: +# default: +# - 1.1.1.1 + +env: + BLOCKY_CONFIG_FILE: "/app/config/" + +blocky: + enableWebUI: true + enablePrometheus: true + +probes: + liveness: + enabled: + custom: true + spec: + exec: + command: + - /app/blocky + - healthcheck + readiness: + custom: true + spec: + exec: + command: + - /app/blocky + - healthcheck + startup: + custom: true + spec: + exec: + command: + - /app/blocky + - healthcheck + +service: + main: + ports: + main: + port: 10315 + protocol: HTTP + targetPort: 80 + dns-tcp: + enabled: true + ports: + dns-tcp: + enabled: true + port: 53 + targetPort: 53 + dns-udp: + enabled: true + ports: + dns-udp: + enabled: true + port: 53 + protocol: UDP + targetPort: 53 + dot: + enabled: true + ports: + dot: + enabled: true + port: 853 + protocol: TCP + targetPort: 853 + http: + enabled: true + ports: + http: + enabled: true + port: 4000 + protocol: HTTP + targetPort: 4000 + https: + enabled: true + ports: + https: + enabled: true + port: 4443 + protocol: HTTPS + targetPort: 4443 + k8sgateway: + enabled: true + ports: + k8sgateway: + enabled: true + port: 5353 + protocol: UDP + targetPort: 5353 + +## TODO Add support for SCALE certificates and certificates secrets here +certFile: "" +keyFile: "" +logLevel: info +logFormat: text +logTimestamp: true +logPrivacy: false +dohUserAgent: "" +minTlsServeVersion: 1.2 + +# -- set the default DNS upstream servers +# Primarily designed for inclusion in the TrueNAS SCALE GUI +defaultUpstreams: + - 1.1.1.1 + - 1.0.0.1 + - 8.8.8.8 + - 8.8.4.4 + - 9.9.9.9 + - 149.112.112.112 + - 208.67.222.222 + - 208.67.220.220 + - 8.26.56.26 + - 8.20.247.20 + - 185.228.168.9 + - 185.228.169.9 + - 76.76.19.19 + - 76.223.122.150 + - 76.76.2.0 + - 76.76.10.0 + +# -- set additional upstreams +# Primarily designed for inclusion in the TrueNAS SCALE GUI +upstreams: + # - name: group2 + # dnsservers: + # - 1.1.1.1 + +# -- set bootstrap dns (not needed) +# Ensures bootstrap encryption and ensure it doesn't use k8s dns +bootstrapDns: + # -- Upstream + upstream: "" + # -- IP's linked to upstream DoT/DoH DNS name + ips: [] + +# -- Return empty answer for these queries +filtering: + # -- Ensures filtering by query type + queryTypes: [] + +# -- Set manual custom DNS resolution +customDNS: + customTTL: 1h + filterUnmappedTypes: true + rewrite: [] + # - in: something.com + # out: somethingelse.com + mapping: [] + # - domain: something.com + # dnsserver: 192.168.178.1 + +# -- Setup client-name lookup +clientLookup: + # -- upstream used for client-name lookup + upstream: "" + singleNameOrder: [] + clients: + # - domain: laptop + # ips: [] + +# -- Setup caching +caching: + minTime: 5m + maxTime: 30m + maxItemsCount: 0 + prefetching: false + prefetchExpires: 2h + prefetchThreshold: 5 + prefetchMaxItemsCount: 0 + cacheTimeNegative: 30m + +# -- set conditional settings +# Primarily designed for inclusion in the TrueNAS SCALE GUI +conditional: + rewrite: [] + # - in: something.com + # out: somethingelse.com + mapping: [] + # - domain: something.com + # dnsserver: 192.168.178.1 + +# -- set blocking settings using Lists +# Primarily designed for inclusion in the TrueNAS SCALE GUI +blocking: + # -- Sets the blocktype + blockType: nxDomain + # -- Sets the block ttl + blockTTL: 6h + # -- Sets the block refreshPeriod + refreshPeriod: 4h + # -- Sets the block download timeout + downloadTimeout: 60s + # -- Sets the block download attempt count + downloadAttempts: 3 + # -- Sets the block download cooldown + downloadCooldown: 2s + # -- Set to fail start of lists cannot be downloaded + failStartOnListError: false + # -- Sets how many list-groups can be processed at the same time + processingConcurrency: 4 + # -- Add blocky whitelists + whitelist: [] + # - name: ads + # lists: + # - https://someurl.com/list.txt + # - /somefile.txt + + # -- Blocky blacklists + blacklist: [] + # - name: ads + # lists: + # - https://someurl.com/list.txt + # - /somefile.txt + + # -- Blocky clientGroupsBlock + clientGroupsBlock: [] + # - name: default + # groups: + # - ads + +# -- configure using hostsfile for lookups +# Allows for using the hosts configured in kubernetes and such +hostsFile: + enabled: false + filePath: /etc/hosts + hostsTTL: 60m + refreshPeriod: 30m + +## TODO: add this with postgresql support as well +# queryLog: +# type: csv +# target: /logs +# logRetentionDays: 0 +# creationAttempts: 3 +# CreationCooldown: 2 + +portal: + enabled: true + +serviceAccount: + main: + # -- Specifies whether a service account should be created + enabled: true + +# -- Create a ClusterRole and ClusterRoleBinding +# @default -- See below +rbac: + main: + # -- Enables or disables the ClusterRole and ClusterRoleBinding + enabled: true + + # -- Set Rules on the ClusterRole + rules: + - apiGroups: + - "" + resources: + - services + - namespaces + verbs: + - list + - watch + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingresses + verbs: + - list + - watch + +k8sgateway: + enabled: true + # -- TTL for non-apex responses (in seconds) + ttl: 300 + + # -- Limit what kind of resources to watch, e.g. watchedResources: ["Ingress"] + watchedResources: [] + + # -- Service name of a secondary DNS server (should be `serviceName.namespace`) + secondary: "" + + # -- Override the default `serviceName.namespace` domain apex + apex: "" + + # -- list of processed domains + domains: [] + # -- Delegated domain + # - domain: "example.com" + # # -- Optional configuration option for DNS01 challenge that will redirect all acme + # # challenge requests to external cloud domain (e.g. managed by cert-manager) + # # See: https://cert-manager.io/docs/configuration/acme/dns01/ + # dnsChallenge: + # enabled: false + # domain: dns01.clouddns.com + + forward: + enabled: false + primary: tls://1.1.1.1 + secondary: tls://1.0.0.1 + options: + - name: tls_servername + value: cloudflare-dns.com + +unbound: + enabled: false + +redis: + enabled: true + existingSecret: "rediscreds" diff --git a/cspell.config.yaml b/cspell.config.yaml index 26abe2b759c..f26edaa71b8 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -21,6 +21,8 @@ words: - basicauthexample - batnoter - bazarr + - Blocklists + - blocktype - bugfixer's - bungeecord - cacher @@ -28,12 +30,15 @@ words: - cheatsheet - chronos - cifs + - clouddns - cloudflared - cloudflareddns - collabora - configfile + - configfiles - configmap - containo + - cooldown - crossplay - csgo - cuda @@ -46,6 +51,8 @@ words: - ddns - djava - dnat + - dnsserver + - dnsservers - dockerized - duplicati - dynmap @@ -65,6 +72,7 @@ words: - gibibyte - gluster - goauthentik + - Groupname - gunicorn - healthcheck - healthchecks @@ -196,9 +204,13 @@ words: - Scipy - selfsigned - serverconfig + - servername - serverstransports - serviceexpert + - sgateway - smallblock + - somefile + - somethingelse - sonarr - sonarrsabnzbd - sonatype @@ -235,6 +247,7 @@ words: - umami - unet - unifi + - Upstreams - userspace - vaapi - valheim