From 98c2fa62403ca6a48b2e3198f5a0d86030da2ddd Mon Sep 17 00:00:00 2001 From: "noussair.hamrit" Date: Mon, 16 Jun 2025 16:29:39 +0200 Subject: [PATCH] =?UTF-8?q?Initial=20commit=20-=20update=20else=20conditio?= =?UTF-8?q?n=20on=20the=20line=20239=20in=20templates/Deployment=20-=20upd?= =?UTF-8?q?ate=20=20st-common=20version=20from=200.1.10=20to=200.1.12=20on?= =?UTF-8?q?=20Chart.yaml=20file=20-=20add=20gitlab=20ci/cd=20pipeline=20to?= =?UTF-8?q?=20=20package=20the=20Helm=20chart=20into=20a=20.tgz.=20and=20P?= =?UTF-8?q?ublish=20it=20to=20GitLab=E2=80=99s=20Helm=20package=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 40 + Chart.lock | 9 + Chart.yaml | 34 + README.md | 325 ++++++ archive/coa-master | 55 + archive/tls-master | 247 +++++ charts/mariadb-20.5.9.tgz | Bin 0 -> 77309 bytes charts/st-common-0.1.12.tgz | Bin 0 -> 23576 bytes files/docker-entrypoint-initdb.d/README.md | 3 + files/mods-available/sql | 366 +++++++ files/schema/mysql.sql | 165 +++ files/sites-available/coa | 41 + files/sites-available/default | 1072 ++++++++++++++++++++ files/sites-available/dhcp | 595 +++++++++++ files/sites-available/inner-tunnel | 126 +++ files/sites-available/status | 126 +++ files/sites-available/tls | 603 +++++++++++ index.yaml | 115 +++ templates/Certificate.yaml | 54 + templates/ConfigMap/clients.yaml | 23 + templates/ConfigMap/envvars.yaml | 75 ++ templates/ConfigMap/mods-enabled.yaml | 21 + templates/ConfigMap/sites-enabled.yaml | 29 + templates/Deployment.yaml | 366 +++++++ templates/Istio/Gateway.yaml | 69 ++ templates/Istio/VirtualService.yaml | 47 + templates/NetworkPolicy.yaml | 57 ++ templates/PersistentVolumeClaim.yaml | 38 + templates/PodDisruptionBudget.yaml | 28 + templates/PrometheusRule.yaml | 25 + templates/Role.yaml | 29 + templates/RoleBinding.yaml | 26 + templates/Secret/credentials.yaml | 38 + templates/Secret/sql-tls.yaml | 30 + templates/Secret/tls.yaml | 30 + templates/Service.yaml | 98 ++ templates/ServiceAccount.yaml | 27 + templates/_helpers/_databases.tpl | 95 ++ templates/_helpers/_helpers.tpl | 140 +++ templates/_helpers/_images.tpl | 14 + templates/_helpers/_names.tpl | 10 + templates/_helpers/_volumes.tpl | 18 + values-test.yaml | 44 + values.yaml | 985 ++++++++++++++++++ 44 files changed, 6338 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 Chart.lock create mode 100644 Chart.yaml create mode 100644 README.md create mode 100644 archive/coa-master create mode 100644 archive/tls-master create mode 100644 charts/mariadb-20.5.9.tgz create mode 100644 charts/st-common-0.1.12.tgz create mode 100644 files/docker-entrypoint-initdb.d/README.md create mode 100644 files/mods-available/sql create mode 100644 files/schema/mysql.sql create mode 100644 files/sites-available/coa create mode 100644 files/sites-available/default create mode 100644 files/sites-available/dhcp create mode 100644 files/sites-available/inner-tunnel create mode 100644 files/sites-available/status create mode 100644 files/sites-available/tls create mode 100644 index.yaml create mode 100644 templates/Certificate.yaml create mode 100644 templates/ConfigMap/clients.yaml create mode 100644 templates/ConfigMap/envvars.yaml create mode 100644 templates/ConfigMap/mods-enabled.yaml create mode 100644 templates/ConfigMap/sites-enabled.yaml create mode 100644 templates/Deployment.yaml create mode 100644 templates/Istio/Gateway.yaml create mode 100644 templates/Istio/VirtualService.yaml create mode 100644 templates/NetworkPolicy.yaml create mode 100644 templates/PersistentVolumeClaim.yaml create mode 100644 templates/PodDisruptionBudget.yaml create mode 100644 templates/PrometheusRule.yaml create mode 100644 templates/Role.yaml create mode 100644 templates/RoleBinding.yaml create mode 100644 templates/Secret/credentials.yaml create mode 100644 templates/Secret/sql-tls.yaml create mode 100644 templates/Secret/tls.yaml create mode 100644 templates/Service.yaml create mode 100644 templates/ServiceAccount.yaml create mode 100644 templates/_helpers/_databases.tpl create mode 100644 templates/_helpers/_helpers.tpl create mode 100644 templates/_helpers/_images.tpl create mode 100644 templates/_helpers/_names.tpl create mode 100644 templates/_helpers/_volumes.tpl create mode 100644 values-test.yaml create mode 100644 values.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..ee96660 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +default: + tags: + - docker-test + +stages: + - package + - publish + +variables: + CHART_NAME: "freeradius" + CHART_VERSION: "1.0.3" + PACKAGE_PATH: "packages" + HELM_EXPERIMENTAL_OCI: "1" + +package_chart: + stage: package + image: + name: alpine/helm:3.14.0 + entrypoint: [""] + script: + - helm repo add startechnica https://startechnica.github.io/apps + - helm dependency build . + - mkdir -p $PACKAGE_PATH + - helm package . --destination $PACKAGE_PATH + artifacts: + paths: + - ${PACKAGE_PATH}/${CHART_NAME}-${CHART_VERSION}.tgz + + +publish_chart: + stage: publish + image: alpine/curl:8.14.1 + script: + - | + curl --fail-with-body --request POST \ + --user gitlab-ci-token:$CI_JOB_TOKEN \ + --form "chart=@${PACKAGE_PATH}/${CHART_NAME}-${CHART_VERSION}.tgz" \ + "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts" + only: + - main \ No newline at end of file diff --git a/Chart.lock b/Chart.lock new file mode 100644 index 0000000..57da691 --- /dev/null +++ b/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: st-common + repository: https://startechnica.github.io/apps + version: 0.1.12 +- name: mariadb + repository: oci://registry-1.docker.io/bitnamicharts + version: 20.5.9 +digest: sha256:03a311ace58596d28267595eab6c85c5d39af13a5864dbe04be8931a2262b6c7 +generated: "2025-06-16T16:20:04.252816273+02:00" diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..50572a4 --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,34 @@ +annotations: + category: AccessManagement +apiVersion: v2 +appVersion: 3.2.7 +dependencies: +- name: st-common + repository: https://startechnica.github.io/apps + version: 0.1.12 +- condition: mariadb.enabled + name: mariadb + repository: oci://registry-1.docker.io/bitnamicharts + version: 20.x.x +description: FreeRADIUS is a modular, high performance free RADIUS suite developed + and distributed under the GNU General Public License, version 2, and is free for + download and use. +home: https://github.com/startechnica/apps/tree/main/charts/freeradius +icon: https://freeradius.org/img/wordmark.svg +keywords: +- freeradius +- radius +- mysql +- postgresql +- ldap +kubeVersion: '>=1.24.0-0' +maintainers: +- email: firmansyah@nainggolan.id + name: firmansyahn + url: https://firmansyah.nainggolan.id +name: freeradius +sources: +- https://freeradius.org/ +- https://github.com/FreeRADIUS/freeradius-server +type: application +version: 1.0.3 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1bcf2ae --- /dev/null +++ b/README.md @@ -0,0 +1,325 @@ + + +# Helm chart for FreeRADIUS + +FreeRADIUS is a modular, high performance free RADIUS suite developed and distributed under the GNU General Public License, version 2, and is free for download and use. + +[Overview of FreeRADIUS](https://freeradius.org/) + +**This chart is not maintained by the upstream project and any issues with the chart should be raised [here](https://github.com/startechnica/apps/issues/new/choose)** + +## TL;DR + +```console +helm repo add startechnica https://startechnica.github.io/apps +helm install my-release startechnica/freeradius +``` + +## Prerequisites + +- Kubernetes 1.22+ +- Helm 3.10.0+ + +## Installing the Chart + +To install the chart with the release name `my-release` on `my-release` namespace: + +```console +helm repo add startechnica https://startechnica.github.io/apps +helm install my-release startechnica/freeradius --namespace my-release --create-namespace +``` + +These commands deploy FreeRADIUS on the Kubernetes cluster in the default configuration. + +> **Tip**: List all releases using `helm list -A` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```console +helm delete my-release --namespace my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Global parameters + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | +| `global.namespaceOverride` | Override the namespace for resource deployed by the chart, but can itself be overridden by the local namespaceOverride | `""` | + + +### Common parameters + +| Name | Description | Value | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------- | +| `kubeVersion` | Force target Kubernetes version (using Helm capabilities if not set) | `""` | +| `nameOverride` | String to partially override common.names.fullname template with a string (will prepend the release name) | `""` | +| `namespaceOverride` | String to fully override common.names.namespace | `""` | +| `fullnameOverride` | String to fully override common.names.fullname template with a string | `""` | +| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | +| `commonLabels` | Labels to add to all deployed objects | `{}` | +| `schedulerName` | Name of the Kubernetes scheduler (other than default) | `""` | +| `clusterDomain` | Kubernetes DNS Domain name to use | `cluster.local` | +| `extraDeploy` | Array of extra objects to deploy with the release (evaluated as a template) | `[]` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the deployment | `[]` | +| `diagnosticMode.args` | Args to override all containers in the deployment | `[]` | + + +### FreeRADIUS parameters + +| Name | Description | Value | +| ----------------------------------------------| -------------------------------------------------------------------------------------------------------------------------| -------------------------------| +| `image.registry` | FreeRADIUS image registry | `docker.io` | +| `image.repository` | FreeRADIUS image repository | `freeradius/freeradius-server` | +| `image.tag` | FreeRADIUS image tag (immutable tags are recommended) | `3.2.3` | +| `image.pullPolicy` | FreeRADIUS image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Set to true if you would like to see extra information on logs | `false` | +| `hostAliases` | Deployment pod host aliases | `[]` | +| `command` | Override default container command (useful when using custom images) | `[]` | +| `args` | Override default container args (useful when using custom images) | `[]` | +| `extraEnvVars` | Extra environment variables to be set on FreeRADIUS containers | `[]` | +| `extraEnvVarsCM` | ConfigMap with extra environment variables | `""` | +| `extraEnvVarsSecret` | Secret with extra environment variables | `""` | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.clusterIP` | Specific cluster IP when service type is cluster IP. Use `None` for headless service | `""` | +| `service.ports.auth` | FreeRADIUS Authentication and Authorization service port | `1812` | +| `service.ports.acct` | FreeRADIUS Accounting service port | `1813` | +| `service.ports.coa` | FreeRADIUS CoA service port | `3799` | +| `service.ports.radsec` | FreeRADIUS RadSec service port | `2083` | +| `service.ports.status` | FreeRADIUS Status service port | `18121` | +| `service.nodePorts.auth` | Specify the nodePort value for the LoadBalancer and NodePort for Authentication service types. | `""` | +| `service.nodePorts.acct` | Specify the nodePort value for the LoadBalancer and NodePort for Accounting service types. | `""` | +| `service.nodePorts.coa` | Specify the nodePort value for the LoadBalancer and NodePort for CoA service types. | `""` | +| `service.nodePorts.radsec` | Specify the nodePort value for the LoadBalancer and NodePort for RadSec service types. | `""` | +| `service.nodePorts.status` | Specify the nodePort value for the LoadBalancer and NodePort for Status service types. | `""` | +| `service.extraPorts` | Extra ports to expose (normally used with the `sidecar` value) | `[]` | +| `service.externalIPs` | External IP list to use with ClusterIP service type | `[]` | +| `service.loadBalancerIP` | `loadBalancerIP` if service type is `LoadBalancer` | `""` | +| `service.loadBalancerSourceRanges` | Addresses that are allowed when svc is `LoadBalancer` | `[]` | +| `service.externalTrafficPolicy` | FreeRADIUS service external traffic policy | `Cluster` | +| `service.annotations` | Additional annotations for FreeRADIUS service | `{}` | +| `service.sessionAffinity` | Session Affinity for Kubernetes service, can be `None` or `ClientIP` | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `serviceAccount.create` | Specify whether a ServiceAccount should be created | `false` | +| `serviceAccount.name` | Name of the service account to use. If not set and create is true, a name is generated using the fullname template. | `""` | +| `serviceAccount.automountServiceAccountToken` | Automount service account token for the server service account | `false` | +| `serviceAccount.annotations` | Annotations for service account. Evaluated as a template. Only used if `create` is `true`. | `{}` | +| `command` | Override default container command (useful when using custom images) | `[]` | +| `extraEnvVars` | Array containing extra env vars to configure FreeRADIUS | `[]` | +| `extraEnvVarsCM` | ConfigMap containing extra env vars to configure FreeRADIUS | `""` | +| `extraEnvVarsSecret` | Secret containing extra env vars to configure FreeRADIUS | `""` | +| `rbac.create` | Specify whether RBAC resources should be created and used | `false` | +| `podSecurityContext.enabled` | Enable security context | `true` | +| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `101` | +| `podSecurityContext.runAsUser` | User ID for the container | `101` | +| `containerSecurityContext.enabled` | Enabled FreeRADIUS container Security Context | `true` | +| `containerSecurityContext.runAsUser` | Set FreeRADIUS container Security Context runAsUser | `101` | +| `containerSecurityContext.runAsNonRoot` | Set FreeRADIUS container Security Context runAsNonRoot | `true` | +| `tls.enabled` | Enable TLS support for replication traffic | `false` | +| `tls.autoGenerated` | Generate automatically self-signed TLS certificates | `false` | +| `tls.autoGenerator.certmanager.enabled` | | `false` | +| `tls.certificatesSecret` | Name of the secret that contains the certificates | `"false"` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename | `""` | +| `configuration` | Configuration for the FreeRADIUS server (`radiusd.conf`) | `""` | +| `configurationConfigMap` | ConfigMap with the FreeRADIUS configuration files (Note: Overrides `configuration`). The value is evaluated as a template. | `""` | +| `initdbScripts` | Specify dictionary of scripts to be run at first boot | `{}` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) | `""` | +| `extraFlags` | FreeRADIUS additional command line flags | `""` | +| `replicaCount` | Desired number of cluster nodes | `3` | +| `podLabels` | Extra labels for FreeRADIUS pods | `{}` | +| `podAnnotations` | Annotations for FreeRADIUS pods | `{}` | +| `podAffinityPreset` | Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `nodeAffinityPreset.type` | Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `nodeAffinityPreset.key` | Node label key to match. Ignored if `affinity` is set. | `""` | +| `nodeAffinityPreset.values` | Node label values to match. Ignored if `affinity` is set. | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `topologySpreadConstraints` | Topology Spread Constraints for pods assignment | `[]` | +| `lifecycleHooks` | for the galera container(s) to automate configuration before or after startup | `{}` | +| `containerPorts.auth` | Auth database container port | `1812` | +| `containerPorts.acct` | Acct cluster container port | `1813` | +| `containerPorts.coa` | CoA container port | `3799` | +| `containerPorts.radsec` | RadSec container port | `2083` | +| `containerPorts.status` | Status container port | `18121` | +| `persistence.enabled` | Enable persistence using PVC | `true` | +| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim` | `""` | +| `persistence.subPath` | Subdirectory of the volume to mount | `""` | +| `persistence.mountPath` | Path to mount the volume at | `/startechnica/freeradius` | +| `persistence.selector` | Selector to match an existing Persistent Volume (this value is evaluated as a template) | `{}` | +| `persistence.storageClass` | Persistent Volume Storage Class | `""` | +| `persistence.annotations` | Persistent Volume Claim annotations | `{}` | +| `persistence.labels` | Persistent Volume Claim Labels | `{}` | +| `persistence.accessModes` | Persistent Volume Access Modes | `["ReadWriteOnce"]` | +| `persistence.size` | Persistent Volume Size | `8Gi` | +| `priorityClassName` | Priority Class Name for Statefulset | `""` | +| `initContainers` | Additional init containers (this value is evaluated as a template) | `[]` | +| `sidecars` | Add additional sidecar containers (this value is evaluated as a template) | `[]` | +| `extraVolumes` | Extra volumes | `[]` | +| `extraVolumeMounts` | Mount extra volume(s) | `[]` | +| `resources.limits` | The resources limits for the container | `{}` | +| `resources.requests` | The requested resources for the container | `{}` | +| `livenessProbe.enabled` | Turn on and off liveness probe | `true` | +| `livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | `120` | +| `livenessProbe.periodSeconds` | How often to perform the probe | `10` | +| `livenessProbe.timeoutSeconds` | When the probe times out | `1` | +| `livenessProbe.failureThreshold` | Minimum consecutive failures for the probe | `3` | +| `livenessProbe.successThreshold` | Minimum consecutive successes for the probe | `1` | +| `readinessProbe.enabled` | Turn on and off readiness probe | `true` | +| `readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | `30` | +| `readinessProbe.periodSeconds` | How often to perform the probe | `10` | +| `readinessProbe.timeoutSeconds` | When the probe times out | `1` | +| `readinessProbe.failureThreshold` | Minimum consecutive failures for the probe | `3` | +| `readinessProbe.successThreshold` | Minimum consecutive successes for the probe | `1` | +| `startupProbe.enabled` | Turn on and off startup probe | `false` | +| `startupProbe.initialDelaySeconds` | Delay before startup probe is initiated | `120` | +| `startupProbe.periodSeconds` | How often to perform the probe | `10` | +| `startupProbe.timeoutSeconds` | When the probe times out | `1` | +| `startupProbe.failureThreshold` | Minimum consecutive failures for the probe | `48` | +| `startupProbe.successThreshold` | Minimum consecutive successes for the probe | `1` | +| `customStartupProbe` | Custom liveness probe for the Web component | `{}` | +| `customLivenessProbe` | Custom liveness probe for the Web component | `{}` | +| `customReadinessProbe` | Custom rediness probe for the Web component | `{}` | +| `podDisruptionBudget.create` | Specifies whether a Pod disruption budget should be created | `false` | +| `podDisruptionBudget.minAvailable` | Minimum number / percentage of pods that should remain scheduled | `1` | +| `podDisruptionBudget.maxUnavailable` | Maximum number / percentage of pods that may be made unavailable | `""` | +| `metrics.enabled` | Start a side-car prometheus exporter | `false` | +| `metrics.image.registry` | FreeRADIUS Prometheus exporter image registry | `""` | +| `metrics.image.repository` | FreeRADIUS Prometheus exporter image repository | `""` | +| `metrics.image.tag` | FreeRADIUS Prometheus exporter image tag (immutable tags are recommended) | `""` | +| `metrics.image.pullPolicy` | FreeRADIUS Prometheus exporter image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | FreeRADIUS Prometheus exporter image pull secrets | `[]` | +| `metrics.extraFlags` | FreeRADIUS Prometheus exporter additional command line flags | `[]` | +| `metrics.resources.limits` | The resources limits for the container | `{}` | +| `metrics.resources.requests` | The requested resources for the container | `{}` | +| `metrics.service.type` | Prometheus exporter service type | `ClusterIP` | +| `metrics.service.port` | Prometheus exporter service port | `9104` | +| `metrics.service.annotations` | Prometheus exporter service annotations | `{}` | +| `metrics.service.loadBalancerIP` | Load Balancer IP if the Prometheus metrics server type is `LoadBalancer` | `""` | +| `metrics.service.clusterIP` | Prometheus metrics service Cluster IP | `""` | +| `metrics.service.loadBalancerSourceRanges` | Prometheus metrics service Load Balancer sources | `[]` | +| `metrics.service.externalTrafficPolicy` | Prometheus metrics service external traffic policy | `Cluster` | +| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) | `false` | +| `metrics.serviceMonitor.namespace` | Optional namespace which Prometheus is running in | `""` | +| `metrics.serviceMonitor.jobLabel` | The name of the label on the target service to use as the job name in prometheus. | `""` | +| `metrics.serviceMonitor.interval` | How frequently to scrape metrics (use by default, falling back to Prometheus' default) | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.selector` | ServiceMonitor selector labels | `{}` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `metrics.serviceMonitor.honorLabels` | honorLabels chooses the metric's labels on collisions with target labels | `false` | +| `metrics.serviceMonitor.labels` | ServiceMonitor extra labels | `{}` | +| `metrics.prometheusRules.enabled` | if `true`, creates a Prometheus Operator PrometheusRule (also requires `metrics.enabled` to be `true`, and makes little sense without ServiceMonitor) | `false` | +| `metrics.prometheusRules.additionalLabels` | Additional labels to add to the PrometheusRule so it is picked up by the operator | `{}` | +| `metrics.prometheusRules.rules` | PrometheusRule rules to configure | `{}` | + + +### Custom FreeRADIUS enabled mods parameters + +| Name | Description | Value | +| ------------------------------------------ | --------------------------------------------------- | ----------------- | +| `modsEnabled.sql.enabled` | Enable FreeRADIUS SQL module | `false` | +| `modsEnabled.sql.dialect` | The driver module used to execute the queries. | `mysql` | +| `modsEnabled.sql.table.acct1` | Tables containing 'accounting' items | `radacct` | +| `modsEnabled.sql.table.acct2` | Tables containing 'accounting' items | `radacct` | +| `modsEnabled.sql.table.authcheck` | Tables containing 'check' items | `radcheck` | +| `modsEnabled.sql.table.authreply` | Tables containing 'reply' items | `radreply` | +| `modsEnabled.sql.table.client` | Table to keep radius client info | `nas` | +| `modsEnabled.sql.table.groupcheck` | Tables containing 'check' items | `radgroupcheck` | +| `modsEnabled.sql.table.groupreply` | Tables containing 'reply' items | `radgroupreply` | +| `modsEnabled.sql.table.postauth` | Allow for storing data after authentication | `radpostauth` | +| `modsEnabled.sql.table.usergroup` | Table to keep group info | `radusergroup` | +| `modsEnabled.sql.tls.enabled` | Enable FreeRADIUS SQL TLS module | `false` | +| `modsEnabled.sql.tls.autoGenerated` | | `false` | +| `modsEnabled.sql.tls.certificatesSecret` | | `""` | +| `modsEnabled.sql.tls.certFilename` | | `""` | +| `modsEnabled.sql.tls.certKeyFilename` | | `""` | +| `modsEnabled.sql.tls.certCAFilename` | | `""` | +| `modsEnabled.sql.tls.existingTlsSecret` | | `""` | +| `modsEnabled.sql.tls.privateKeyPassword` | | `""` | + + +### Custom FreeRADIUS enabled sites parameters + +| Name | Description | Value | +| ------------------------------------------ | ------------------------------------------------------------------------------- | ----------------- | +| `sitesEnabled.coa.enabled` | Enable FreeRADIUS coa service | `false` | +| `sitesEnabled.status.enabled` | Enable FreeRADIUS status service | `true` | +| `sitesEnabled.tls.enabled` | Enable FreeRADIUS radsec service | `false` | +| `sitesEnabled.tls.cipher` | | `false` | +| `sitesEnabled.tls.privateKeyPassword` | | `false` | + + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +helm install my-release \ + --set imagePullPolicy=Always \ + startechnica/freeradius +``` + +The above command sets the `imagePullPolicy` to `Always`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +helm install my-release startechnica/freeradius -f values.yaml +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Configuration and installation details + +### Adding extra environment variables + +In case you want to add extra environment variables (useful for advanced operations like custom init scripts), you can use the `extraEnvVars` property. + +```yaml +extraEnvVars: + - name: LOG_LEVEL + value: error +``` + +Alternatively, you can use a ConfigMap or a Secret with the environment variables. To do so, use the `extraEnvVarsCM` or the `extraEnvVarsSecret` values. + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `affinity` parameter. Find more information about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +### Deploying extra resources + +There are cases where you may want to deploy extra objects, such a ConfigMap containing your app's configuration or some extra deployment with a micro service used by your app. For covering this case, the chart allows adding the full specification of other objects using the `extraDeploy` parameter. + +## Troubleshooting + +Find more information about how to deal with common errors related to Startechnica's Helm charts in [this troubleshooting guide](https://startechnica.github.io/doc/troubleshoot-helm-chart-issues). + +## Upgrading + +## License + +Copyright © 2023 Startechnica + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/archive/coa-master b/archive/coa-master new file mode 100644 index 0000000..039e3d8 --- /dev/null +++ b/archive/coa-master @@ -0,0 +1,55 @@ +# -*- text -*- +###################################################################### +# +# Sample virtual server for receiving a CoA or Disconnect-Request packet. +# +server coa { + namespace = $ENV{FREERADIUS_SITES_NAMESPACE} + + # Listen on the CoA port. + # + # This uses the normal set of clients, with the same secret as for + # authentication and accounting. + # + listen { + type = CoA-Request + type = Disconnect-Request + + transport = udp + + udp { + ipaddr = $ENV{FREERADIUS_SITES_COA_LISTEN} + port = $ENV{FREERADIUS_SITES_COA_PORT} + } + } + + # Receive a CoA request + recv CoA-Request { + ok + } + + # Send a CoA ACK + send CoA-ACK { + ok + } + + # Send a CoA NAK + send CoA-NAK { + ok + } + + # Receive a Disconnect request + recv Disconnect-Request { + ok + } + + # Send a Disconnect ACK + send Disconnect-ACK { + ok + } + + # Send a Disconnect NAK + send Disconnect-NAK { + ok + } +} \ No newline at end of file diff --git a/archive/tls-master b/archive/tls-master new file mode 100644 index 0000000..5271f55 --- /dev/null +++ b/archive/tls-master @@ -0,0 +1,247 @@ +###################################################################### +# +# RADIUS over TLS +# +###################################################################### + +server radsec { + listen { + transport = tls + + type = Access-Request + type = Accounting-Request + + tls { + + ipaddr = $ENV{FREERADIUS_SITES_TLS_LISTEN} + port = $ENV{FREERADIUS_SITES_TLS_PORT} + + # Connection limiting for sockets with "proto = tcp". + # + limit { + # Limit the number of simultaneous TCP connections to the socket + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # The lifetime, in seconds, of a TCP connection. After this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been received over the connection for this time, the connection will be closed. + # Setting this to 0 means "no timeout". + # + # We STRONGLY RECOMMEND that you set an idle timeout. + idle_timeout = 30 + } + + private_key_password = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_PASSWORD} + private_key_file = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_FILE} + + # If Private key & Certificate are located in the same file, then private_key_file & + # certificate_file must contain the same file name. + # + # If ca_file (below) is not used, then the certificate_file below MUST include not only the server certificate, but ALSO all + # of the CA certificates used to sign the server certificate. + certificate_file = $ENV{FREERADIUS_SITES_TLS_CERTIFICATE_FILE} + + # Trusted Root CA list + # + # ALL of the CA's in this list will be trusted to issue client certificates for authentication. + # + # In general, you should use self-signed certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain *one* CA certificate. + # + # This parameter is used only for EAP-TLS, when you issue client certificates. If you do not use client certificates, and you do not want + # to permit EAP-TLS authentication, then delete this configuration item. + ca_file = $ENV{FREERADIUS_SITES_TLS_CA_FILE} + + # + # For DH cipher suites to work, you have to run OpenSSL to create the DH file first: + # + # openssl dhparam -out certs/dh 1024 + dh_file = ${certdir}/dh + + # + # If your system doesn't have /dev/urandom, you will need to create this file, and periodically change its contents. + # For security reasons, FreeRADIUS doesn't write to files in its configuration directory. + # random_file = /dev/urandom + + # + # The default fragment size is 1K. However, it's possible to send much more data than that over a TCP connection. The upper limit is 64K. + # Setting the fragment size to more than 1K means that there are fewer round trips when setting up a TLS connection. But only if the certificates are large. + fragment_size = 8192 + + # include_length is a flag which is by default set to yes If set to yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the message is included ONLY in the First packet of a fragment series. + # include_length = yes + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash '. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the line below. + # 5) Restart radiusd + # check_crl = yes + ca_path = ${cadir} + + # Accept an expired Certificate Revocation List + # + # allow_expired_crl = no + + # Accept a not-yet-valid Certificate Revocation List + # + # allow_not_yet_valid_crl = no + + # + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # This check can be done more generally by checking + # the value of the TLS-Client-Cert-Issuer attribute. + # This check can be done via any mechanism you choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # This check can be done more generally by checking + # the value of the TLS-Client-Cert-Common-Name attribute. + # This check can be done via any mechanism you choose. + # + # check_cert_cn = %{User-Name} + # + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + cipher_list = "DEFAULT" + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) + # for choosing right cipher suite rather than + # using client-specified list which is OpenSSl default + # behavior. Having it set to 'yes' is best practice + # for TLS. + cipher_server_preference = yes + + # + # Session resumption / fast reauthentication + # cache. + # + # The cache contains the following information: + # + # session Id - unique identifier, managed by SSL + # User-Name - from the Access-Accept + # Stripped-User-Name - from the Access-Request + # Cached-Session-Policy - from the Access-Accept + # + # The "Cached-Session-Policy" is the name of a + # policy which should be applied to the cached + # session. This policy can be used to assign + # VLANs, IP addresses, etc. It serves as a useful + # way to re-apply the policy from the original + # Access-Accept to the subsequent Access-Accept + # for the cached session. + # + # On session resumption, these attributes are + # copied from the cache, and placed into the + # reply list. + # + # You probably also want "use_tunneled_reply = yes" + # when using fast session resumption. + # + cache { + # + # Lifetime of the cached entries, in hours. + # The sessions will be deleted after this + # time. + # + lifetime = 24 # hours + + # + # Internal "name" of the session cache. + # Used to distinguish which TLS context + # sessions belong to. + # + # The server will generate a random value + # if unset. This will change across server + # restart so you MUST set the "name" if you + # want to persist sessions (see below). + # + # If you use IPv6, change the "ipaddr" below + # to "ipv6addr" + # + #name = "TLS ${..ipaddr} ${..port} ${..proto}" + + # + # Simple directory-based storage of sessions. + # Two files per session will be written, the SSL + # state and the cached VPs. This will persist session + # across server restarts. + # + # The server will need write perms, and the directory + # should be secured from anyone else. You might want + # a script to remove old files from here periodically: + # + # find ${logdir}/tlscache -mtime +2 -exec rm -f {} \; + # + # This feature REQUIRES "name" option be set above. + # + #persist_dir = "${logdir}/tlscache" + } + + # Require a client certificate. + # + require_client_cert = yes + + # + # As of version 2.1.10, client certificates can be validated via an external command. This allows dynamic CRLs or OCSP to be used. + # + # This configuration is commented out in the default configuration. Uncomment it, and configure the correct paths below to enable it. + # + verify { + # A temporary directory where the client certificates are stored. This directory MUST be owned by the UID of the server, + # and MUST not be accessible by any other users. When the server starts, it will do "chmod go-rwx" on the directory, for + # security reasons. The directory MUST exist when the server starts. + # + # You should also delete all of the files in the directory when the server starts. + tmpdir = /startechnica/freeradius/tmp + + # The command used to verify the client cert. We recommend using the OpenSSL command-line tool. + # + # The ${..ca_path} text is a reference to the ca_path variable defined above. + # + # The %{TLS-Client-Cert-Filename} is the name of the temporary file containing the cert in PEM format. This file is automatically + # deleted by the server when the command returns. + # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" + } + } + } + + recv Access-Request { + ok + } + + recv Accounting-Request { + ok + } +} \ No newline at end of file diff --git a/charts/mariadb-20.5.9.tgz b/charts/mariadb-20.5.9.tgz new file mode 100644 index 0000000000000000000000000000000000000000..017dc9cf2be0360d1bd5014f155196e312e47a1b GIT binary patch literal 77309 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 ICICJO04yoU*#H0l literal 0 HcmV?d00001 diff --git a/charts/st-common-0.1.12.tgz b/charts/st-common-0.1.12.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f32e3fe2b4428d1afb40e705660933451643c1c7 GIT binary patch literal 23576 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/main/schema.sql Schema +# sql//main/queries.conf Authorisation and Accounting queries +# +# Where "DB" is mysql, mssql, oracle, or postgresql. +# +# The name used to query SQL is sql_user_name, which is set in the file +# +# raddb/mods-config/sql/main/${dialect}/queries.conf +# +# If you are using realms, that configuration should be changed to use +# the Stripped-User-Name attribute. See the comments around sql_user_name +# for more information. +# + +sql { + # + # The dialect of SQL being used. + # + # Allowed dialects are: + # + # mssql + # mysql + # oracle + # postgresql + # sqlite + # mongo + # +# dialect = "sqlite" + dialect = $ENV{FREERADIUS_MODS_SQL_DIALECT} + + # + # The driver module used to execute the queries. Since we + # don't know which SQL drivers are being used, the default is + # "rlm_sql_null", which just logs the queries to disk via the + # "logfile" directive, below. + # + # In order to talk to a real database, delete the next line, + # and uncomment the one after it. + # + # If the dialect is "mssql", then the driver should be set to + # one of the following values, depending on your system: + # + # rlm_sql_db2 + # rlm_sql_firebird + # rlm_sql_freetds + # rlm_sql_iodbc + # rlm_sql_unixodbc + # +# driver = "rlm_sql_null" + driver = "rlm_sql_${dialect}" + + # + # Driver-specific subsections. They will only be loaded and + # used if "driver" is something other than "rlm_sql_null". + # When a real driver is used, the relevant driver + # configuration section is loaded, and all other driver + # configuration sections are ignored. + # + sqlite { + # Path to the sqlite database + filename = $ENV{FREERADIUS_MODS_SQL_SQLITE_FILENAME} + + # How long to wait for write locks on the database to be released (in ms) before giving up. + busy_timeout = $ENV{FREERADIUS_MODS_SQL_SQLITE_BUSY_TIMEOUT} + + # If the file above does not exist and bootstrap is set + # a new database file will be created, and the SQL statements + # contained within the bootstrap file will be executed. + bootstrap = "${modconfdir}/${..:name}/main/sqlite/schema.sql" + } + + mysql { + # If any of the files below are set, TLS encryption is enabled + tls { +# ca_file = $ENV{FREERADIUS_MODS_SQL_TLS_CACERT} +# ca_path = "/startechnica/freeradius/certs-sql/" +# certificate_file = $ENV{FREERADIUS_MODS_SQL_TLS_CERTIFICATE} +# private_key_file = $ENV{FREERADIUS_MODS_SQL_TLS_PRIVATEKEY} +# cipher = "DHE-RSA-AES256-SHA:AES128-SHA" +# cipher = $ENV{FREERADIUS_MODS_SQL_TLS_CIPHER} + + tls_required = $ENV{FREERADIUS_MODS_SQL_TLS_ENABLE} + tls_check_cert = no + tls_check_cert_cn = no + } + + # If yes, (or auto and libmysqlclient reports warnings are + # available), will retrieve and log additional warnings from + # the server if an error has occured. Defaults to 'auto' + warnings = auto + } + + postgresql { + + # unlike MySQL, which has a tls{} connection configuration, postgresql + # uses its connection parameters - see the radius_db option below in + # this file + + # Send application_name to the postgres server + # Only supported in PG 9.0 and greater. Defaults to no. + send_application_name = yes + } + + # + # Configuration for Mongo. + # + # Note that the Mongo driver is experimental. The FreeRADIUS developers + # are unable to help with the syntax of the Mongo queries. Please see + # the Mongo documentation for that syntax. + # + # The Mongo driver supports only the following methods: + # + # aggregate + # findAndModify + # findOne + # insert + # + # For examples, see the query files: + # + # raddb/mods-config/sql/main/mongo/queries.conf + # raddb/mods-config/sql/main/ippool/queries.conf + # + # In order to use findAndModify with an aggretation pipleline, make + # sure that you are running MongoDB version 4.2 or greater. FreeRADIUS + # assumes that the paramaters passed to the methods are supported by the + # version of MongoDB which it is connected to. + # + mongo { + # + # The application name to use. + # + appname = "freeradius" + + # + # The TLS parameters here map directly to the Mongo TLS configuration + # + tls { + certificate_file = $ENV{FREERADIUS_MODS_SQL_TLS_CERTIFICATE} + certificate_password = $ENV{FREERADIUS_MODS_SQL_TLS_PRIVATEKEY} + ca_file = $ENV{FREERADIUS_MODS_SQL_TLS_CACERT} + ca_dir = /startechnica/freeradius/certs-sql/ + # crl_file = /path/to/file + weak_cert_validation = false + allow_invalid_hostname = false + } + } + + # Connection info: + # + server = $ENV{FREERADIUS_MODS_SQL_SERVER} + port = $ENV{FREERADIUS_MODS_SQL_PORT} + login = $ENV{FREERADIUS_MODS_SQL_LOGIN} + password = $ENV{FREERADIUS_MODS_SQL_PASSWORD} + + # Connection info for Mongo + # Authentication Without SSL + # server = "mongodb://USER:PASSWORD@192.16.0.2:PORT/DATABASE?authSource=admin&ssl=false" + + # Authentication With SSL + # server = "mongodb://USER:PASSWORD@192.16.0.2:PORT/DATABASE?authSource=admin&ssl=true" + + # Authentication with Certificate + # Use this command for retrieve Derived username: + # openssl x509 -in mycert.pem -inform PEM -subject -nameopt RFC2253 + # server = mongodb://@192.168.0.2:PORT/DATABASE?authSource=$external&ssl=true&authMechanism=MONGODB-X509 + + # Database table configuration for everything except Oracle + radius_db = $ENV{FREERADIUS_MODS_SQL_DB} + + # If you are using Oracle then use this instead +# radius_db = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=your_sid)))" + + # If you're using postgresql this can also be used instead of the connection info parameters +# radius_db = "dbname=$ENV{FREERADIUS_MODS_SQL_DB} host=$ENV{FREERADIUS_MODS_SQL_SERVER} port=$ENV{FREERADIUS_MODS_SQL_PORT} user=$ENV{FREERADIUS_MODS_SQL_LOGIN} password=$ENV{FREERADIUS_MODS_SQL_PASSWORD}" + + # Postgreql doesn't take tls{} options in its module config like mysql does - if you want to + # use SSL connections then use this form of connection info parameter +# radius_db = "host=localhost port=5432 dbname=radius user=radius password=raddpass sslmode=verify-full sslcert=/etc/ssl/client.crt sslkey=/etc/ssl/client.key sslrootcert=/etc/ssl/ca.crt" + + # If you want both stop and start records logged to the + # same SQL table, leave this as is. If you want them in + # different tables, put the start table in acct_table1 + # and stop table in acct_table2 + acct_table1 = $ENV{FREERADIUS_MODS_SQL_TABLE_ACCT1} + acct_table2 = $ENV{FREERADIUS_MODS_SQL_TABLE_ACCT2} + + # Allow for storing data after authentication + postauth_table = $ENV{FREERADIUS_MODS_SQL_TABLE_POSTAUTH} + + # Tables containing 'check' items + authcheck_table = $ENV{FREERADIUS_MODS_SQL_TABLE_AUTHCHECK} + groupcheck_table = $ENV{FREERADIUS_MODS_SQL_TABLE_GROUPCHECK} + + # Tables containing 'reply' items + authreply_table = $ENV{FREERADIUS_MODS_SQL_TABLE_AUTHREPLY} + groupreply_table = $ENV{FREERADIUS_MODS_SQL_TABLE_GROUPREPLY} + + # Table to keep group info + usergroup_table = $ENV{FREERADIUS_MODS_SQL_TABLE_USERGROUP} + + # If set to 'yes' (default) we read the group tables unless Fall-Through = no in the reply table. + # If set to 'no' we do not read the group tables unless Fall-Through = yes in the reply table. +# read_groups = yes + + # If set to 'yes' (default) we read profiles unless Fall-Through = no in the groupreply table. + # If set to 'no' we do not read profiles unless Fall-Through = yes in the groupreply table. +# read_profiles = yes + + # Remove stale session if checkrad does not see a double login + delete_stale_sessions = yes + + # Write SQL queries to a logfile. This is potentially useful for tracing + # issues with authorization queries. See also "logfile" directives in + # mods-config/sql/main/*/queries.conf. You can enable per-section logging + # by enabling "logfile" there, or global logging by enabling "logfile" here. + # + # Per-section logging can be disabled by setting "logfile = ''" +# logfile = ${logdir}/sqllog.sql + + # Set the maximum query duration and connection timeout + # for rlm_sql_mysql. +# query_timeout = 5 + + # As of version 3.0, the "pool" section has replaced the + # following configuration items: + # + # num_sql_socks + # connect_failure_retry_delay + # lifetime + # max_queries + + # + # The connection pool is new for 3.0, and will be used in many + # modules, for all kinds of connection-related activity. + # + # When the server is not threaded, the connection pool + # limits are ignored, and only one connection is used. + # + # If you want to have multiple SQL modules re-use the same + # connection pool, use "pool = name" instead of a "pool" + # section. e.g. + # + # sql sql1 { + # ... + # pool { + # ... + # } + # } + # + # # sql2 will use the connection pool from sql1 + # sql sql2 { + # ... + # pool = sql1 + # } + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the database being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + lifetime = 0 + + # idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + idle_timeout = 60 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } + + # Set to 'yes' to read radius clients from the database ('nas' table) + # Clients will ONLY be read on server startup. + # + # A client can be link to a virtual server via the SQL + # module. This link is done via the following process: + # + # If there is no listener in a virtual server, SQL clients + # are added to the global list for that virtual server. + # + # If there is a listener, and the first listener does not + # have a "clients=..." configuration item, SQL clients are + # added to the global list. + # + # If there is a listener, and the first one does have a + # "clients=..." configuration item, SQL clients are added to + # that list. The client { ...} ` configured in that list are + # also added for that listener. + # + # The only issue is if you have multiple listeners in a + # virtual server, each with a different client list, then + # the SQL clients are added only to the first listener. + # + read_clients = $ENV{FREERADIUS_MODS_SQL_READ_CLIENTS} + + # Table to keep radius client info + client_table = $ENV{FREERADIUS_MODS_SQL_TABLE_CLIENT} + + # + # The group attribute specific to this instance of rlm_sql + # + + # This entry should be used for additional instances (sql foo {}) + # of the SQL module. +# group_attribute = "${.:instance}-SQL-Group" + + # This entry should be used for the default instance (sql {}) + # of the SQL module. + group_attribute = $ENV{FREERADIUS_MODS_SQL_GROUP_ATTRIBUTE} + + # Read database-specific queries + $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf +} \ No newline at end of file diff --git a/files/schema/mysql.sql b/files/schema/mysql.sql new file mode 100644 index 0000000..ef78b2b --- /dev/null +++ b/files/schema/mysql.sql @@ -0,0 +1,165 @@ +########################################################################### +# $Id: 41fcccad1c012226d12cc721518fe91e311e55e2 $ # +# # +# schema.sql rlm_sql - FreeRADIUS SQL Module # +# # +# Database schema for MySQL rlm_sql module # +# # +# To load: # +# mysql -uroot -prootpass radius < schema.sql # +# # +# Mike Machado # +########################################################################### + +# +# Table structure for table 'radacct' +# +CREATE TABLE IF NOT EXISTS radacct ( + radacctid bigint(21) NOT NULL auto_increment, + acctsessionid varchar(64) NOT NULL default '', + acctuniqueid varchar(32) NOT NULL default '', + username varchar(64) NOT NULL default '', + realm varchar(64) default '', + nasipaddress varchar(15) NOT NULL default '', + nasportid varchar(32) default NULL, + nasporttype varchar(32) default NULL, + acctstarttime datetime NULL default NULL, + acctupdatetime datetime NULL default NULL, + acctstoptime datetime NULL default NULL, + acctinterval int(12) default NULL, + acctsessiontime int(12) unsigned default NULL, + acctauthentic varchar(32) default NULL, + connectinfo_start varchar(128) default NULL, + connectinfo_stop varchar(128) default NULL, + acctinputoctets bigint(20) default NULL, + acctoutputoctets bigint(20) default NULL, + calledstationid varchar(50) NOT NULL default '', + callingstationid varchar(50) NOT NULL default '', + acctterminatecause varchar(32) NOT NULL default '', + servicetype varchar(32) default NULL, + framedprotocol varchar(32) default NULL, + framedipaddress varchar(15) NOT NULL default '', + framedipv6address varchar(45) NOT NULL default '', + framedipv6prefix varchar(45) NOT NULL default '', + framedinterfaceid varchar(44) NOT NULL default '', + delegatedipv6prefix varchar(45) NOT NULL default '', + class varchar(64) default NULL, + PRIMARY KEY (radacctid), + UNIQUE KEY acctuniqueid (acctuniqueid), + KEY username (username), + KEY framedipaddress (framedipaddress), + KEY framedipv6address (framedipv6address), + KEY framedipv6prefix (framedipv6prefix), + KEY framedinterfaceid (framedinterfaceid), + KEY delegatedipv6prefix (delegatedipv6prefix), + KEY acctsessionid (acctsessionid), + KEY acctsessiontime (acctsessiontime), + KEY acctstarttime (acctstarttime), + KEY acctinterval (acctinterval), + KEY acctstoptime (acctstoptime), + KEY nasipaddress (nasipaddress), + KEY class (class) +) ENGINE = INNODB; + +# +# Table structure for table 'radcheck' +# +CREATE TABLE IF NOT EXISTS radcheck ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +) ENGINE = INNODB; + +# +# Table structure for table 'radgroupcheck' +# +CREATE TABLE IF NOT EXISTS radgroupcheck ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +) ENGINE = INNODB; + +# +# Table structure for table 'radgroupreply' +# +CREATE TABLE IF NOT EXISTS radgroupreply ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +) ENGINE = INNODB; + +# +# Table structure for table 'radreply' +# +CREATE TABLE IF NOT EXISTS radreply ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +) ENGINE = INNODB; + + +# +# Table structure for table 'radusergroup' +# +CREATE TABLE IF NOT EXISTS `radusergroup` ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1', + PRIMARY KEY (id), + KEY username (username(32)) +) ENGINE = INNODB; + +# +# Table structure for table 'radpostauth' +# +# Note: MySQL versions since 5.6.4 support fractional precision timestamps +# which we use here. Replace the authdate definition with the following +# if your software is too old: +# +# authdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +# +CREATE TABLE IF NOT EXISTS radpostauth ( + id int(11) NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + pass varchar(64) NOT NULL default '', + reply varchar(32) NOT NULL default '', + authdate timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + class varchar(64) default NULL, + PRIMARY KEY (id), + KEY username (username), + KEY class (class) +) ENGINE = INNODB; + +# +# Table structure for table 'nas' +# +CREATE TABLE IF NOT EXISTS nas ( + id int(10) NOT NULL auto_increment, + nasname varchar(128) NOT NULL, + shortname varchar(32), + type varchar(30) DEFAULT 'other', + ports int(5), + secret varchar(60) DEFAULT 'secret' NOT NULL, + server varchar(64), + community varchar(50), + description varchar(200) DEFAULT 'RADIUS Client', + PRIMARY KEY (id), + KEY nasname (nasname) +) ENGINE = INNODB; diff --git a/files/sites-available/coa b/files/sites-available/coa new file mode 100644 index 0000000..85849a8 --- /dev/null +++ b/files/sites-available/coa @@ -0,0 +1,41 @@ +# -*- text -*- +###################################################################### +# +# Sample virtual server for receiving a CoA or Disconnect-Request packet. +# + +# Listen on the CoA port. +# +# This uses the normal set of clients, with the same secret as for authentication and accounting. +# + +listen { + type = coa + # ipaddr = $ENV{FREERADIUS_SITES_COA_LISTEN} + ipaddr = * + port = $ENV{FREERADIUS_SITES_COA_PORT} + virtual_server = coa +} + +server coa { + # When a packet is received, it is processed through the recv-coa section. This applies to *both* CoA-Request and Disconnect-Request packets. + recv-coa { + # CoA && Disconnect packets can be proxied in the same way as authentication or accounting packets. + # Just set Proxy-To-Realm, or Home-Server-Pool, and the packets will be proxied. + + # Do proxying based on realms here. You don't need "IPASS" or "ntdomain", as the proxying is based on the Operator-Name attribute. It contains the realm, + # and ONLY the realm (prefixed by a '1') + suffix + + # Insert your own policies here. + ok + } + + # When a packet is sent, it is processed through the send-coa section. This applies to *both* CoA-Request and Disconnect-Request packets. + send-coa { + # Sample module. + ok + } + + # You can use pre-proxy and post-proxy sections here, too. They will be processed for sending && receiving proxy packets. +} \ No newline at end of file diff --git a/files/sites-available/default b/files/sites-available/default new file mode 100644 index 0000000..a5d37af --- /dev/null +++ b/files/sites-available/default @@ -0,0 +1,1072 @@ +###################################################################### +# +# As of 2.0.0, FreeRADIUS supports virtual hosts using the +# "server" section, and configuration directives. +# +# Virtual hosts should be put into the "sites-available" +# directory. Soft links should be created in the "sites-enabled" +# directory to these files. This is done in a normal installation. +# +# If you are using 802.1X (EAP) authentication, please see also +# the "inner-tunnel" virtual server. You will likely have to edit +# that, too, for authentication to work. +# +# $Id: 1926b7cd6e381cebfb809c7e89f8db0808124625 $ +# +###################################################################### +# +# Read "man radiusd" before editing this file. See the section +# titled DEBUGGING. It outlines a method where you can quickly +# obtain the configuration you want, without running into +# trouble. See also "man unlang", which documents the format +# of this file. +# +# This configuration is designed to work in the widest possible +# set of circumstances, with the widest possible number of +# authentication methods. This means that in general, you should +# need to make very few changes to this file. +# +# The best way to configure the server for your local system +# is to CAREFULLY edit this file. Most attempts to make large +# edits to this file will BREAK THE SERVER. Any edits should +# be small, and tested by running the server with "radiusd -X". +# Once the edits have been verified to work, save a copy of these +# configuration files somewhere. (e.g. as a "tar" file). Then, +# make more edits, and test, as above. +# +# There are many "commented out" references to modules such +# as ldap, sql, etc. These references serve as place-holders. +# If you need the functionality of that module, then configure +# it in radiusd.conf, and un-comment the references to it in +# this file. In most cases, those small changes will result +# in the server being able to connect to the DB, and to +# authenticate users. +# +###################################################################### + +server default { +# +# If you want the server to listen on additional addresses, or on +# additional ports, you can use multiple "listen" sections. +# +# Each section make the server listen for only one type of packet, +# therefore authentication and accounting have to be configured in +# different sections. +# +# The server ignore all "listen" section if you are using '-i' and '-p' +# on the command line. +# +listen { + # Type of packets to listen for. + # Allowed values are: + # auth listen for authentication packets + # acct listen for accounting packets + # auth+acct listen for both authentication and accounting packets + # proxy IP to use for sending proxied packets + # detail Read from the detail file. For examples, see + # raddb/sites-available/copy-acct-to-home-server + # status listen for Status-Server packets. For examples, + # see raddb/sites-available/status + # coa listen for CoA-Request and Disconnect-Request + # packets. For examples, see the file + # raddb/sites-available/coa + # + type = auth + + # Note: "type = proxy" lets you control the source IP used for + # proxying packets, with some limitations: + # + # * A proxy listener CANNOT be used in a virtual server section. + # * You should probably set "port = 0". + # * Any "clients" configuration will be ignored. + # + # See also proxy.conf, and the "src_ipaddr" configuration entry + # in the sample "home_server" section. When you specify the + # source IP address for packets sent to a home server, the + # proxy listeners are automatically created. + + # ipaddr/ipv4addr/ipv6addr - IP address on which to listen. + # If multiple ones are listed, only the first one will + # be used, and the others will be ignored. + # + # The configuration options accept the following syntax: + # + # ipv4addr - IPv4 address (e.g.192.0.2.3) + # - wildcard (i.e. *) + # - hostname (radius.example.com) + # Only the A record for the host name is used. + # If there is no A record, an error is returned, + # and the server fails to start. + # + # ipv6addr - IPv6 address (e.g. 2001:db8::1) + # - wildcard (i.e. *) + # - hostname (radius.example.com) + # Only the AAAA record for the host name is used. + # If there is no AAAA record, an error is returned, + # and the server fails to start. + # + # ipaddr - IPv4 address as above + # - IPv6 address as above + # - wildcard (i.e. *), which means IPv4 wildcard. + # - hostname + # If there is only one A or AAAA record returned + # for the host name, it is used. + # If multiple A or AAAA records are returned + # for the host name, only the first one is used. + # If both A and AAAA records are returned + # for the host name, only the A record is used. + # + # ipv4addr = * + # ipv6addr = * + ipaddr = * + + # Port on which to listen. + # Allowed values are: + # integer port number (1812) + # 0 means "use /etc/services for the proper port" + port = $ENV{FREERADIUS_SITES_DEFAULT_AUTH_PORT} + + # Some systems support binding to an interface, in addition + # to the IP address. This feature isn't strictly necessary, + # but for sites with many IP addresses on one interface, + # it's useful to say "listen on all addresses for eth0". + # + # If your system does not support this feature, you will + # get an error if you try to use it. + # +# interface = eth0 + + # Per-socket lists of clients. This is a very useful feature. + # + # The name here is a reference to a section elsewhere in + # radiusd.conf, or clients.conf. Having the name as + # a reference allows multiple sockets to use the same + # set of clients. + # + # If this configuration is used, then the global list of clients + # is IGNORED for this "listen" section. Take care configuring + # this feature, to ensure you don't accidentally disable a + # client you need. + # + # See clients.conf for the configuration of "per_socket_clients". + # +# clients = per_socket_clients + + # + # Set the default UDP receive buffer size. In most cases, + # the default values set by the kernel are fine. However, in + # some cases the NASes will send large packets, and many of + # them at a time. It is then possible to overflow the + # buffer, causing the kernel to drop packets before they + # reach FreeRADIUS. Increasing the size of the buffer will + # avoid these packet drops. + # +# recv_buff = 65536 + + # + # Connection limiting for sockets with "proto = tcp". + # + # This section is ignored for other kinds of sockets. + # + limit { + # + # Limit the number of simultaneous TCP connections to the socket + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # + # The lifetime, in seconds, of a TCP connection. After + # this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been received over the connection for + # this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + # + # We STRONGLY RECOMMEND that you set an idle timeout. + # + idle_timeout = 30 + } +} + +# +# This second "listen" section is for listening on the accounting +# port, too. +# +listen { + ipaddr = * +# ipv6addr = :: + port = $ENV{FREERADIUS_SITES_DEFAULT_ACCT_PORT} + type = acct +# interface = eth0 +# clients = per_socket_clients + + limit { + # The number of packets received can be rate limited via the + # "max_pps" configuration item. When it is set, the server + # tracks the total number of packets received in the previous + # second. If the count is greater than "max_pps", then the + # new packet is silently discarded. This helps the server + # deal with overload situations. + # + # The packets/s counter is tracked in a sliding window. This + # means that the pps calculation is done for the second + # before the current packet was received. NOT for the current + # wall-clock second, and NOT for the previous wall-clock second. + # + # Useful values are 0 (no limit), or 100 to 10000. + # Values lower than 100 will likely cause the server to ignore + # normal traffic. Few systems are capable of handling more than + # 10K packets/s. + # + # It is most useful for accounting systems. Set it to 50% + # more than the normal accounting load, and you can be sure that + # the server will never get overloaded + # +# max_pps = 0 + + # Only for "proto = tcp". These are ignored for "udp" sockets. + # +# idle_timeout = 0 +# lifetime = 0 +# max_connections = 0 + } +} + +# IPv6 versions of the above - read their full config to understand options +listen { + type = auth + ipv6addr = :: # any. ::1 == localhost + port = $ENV{FREERADIUS_SITES_DEFAULT_AUTH_PORT} +# interface = eth0 +# clients = per_socket_clients + limit { + max_connections = 16 + lifetime = 0 + idle_timeout = 30 + } +} + +listen { + ipv6addr = :: + port = $ENV{FREERADIUS_SITES_DEFAULT_ACCT_PORT} + type = acct +# interface = eth0 +# clients = per_socket_clients + limit { + max_pps = 0 + idle_timeout = 0 + lifetime = 0 + max_connections = 0 + } +} + +# Authorization. First preprocess (hints and huntgroups files), +# then realms, and finally look in the "users" file. +# +# Any changes made here should also be made to the "inner-tunnel" +# virtual server. +# +# The order of the realm modules will determine the order that +# we try to find a matching realm. +# +# Make *sure* that 'preprocess' comes before any realm if you +# need to setup hints for the remote radius server +authorize { + # + # Take a User-Name, and perform some checks on it, for spaces and other + # invalid characters. If the User-Name appears invalid, reject the + # request. + # + # See policy.d/filter for the definition of the filter_username policy. + # + filter_username + + # + # Some broken equipment sends passwords with embedded zeros. + # i.e. the debug output will show + # + # User-Password = "password\000\000" + # + # This policy will fix it to just be "password". + # +# filter_password + + # + # The preprocess module takes care of sanitizing some bizarre + # attributes in the request, and turning them into attributes + # which are more standard. + # + # It takes care of processing the 'raddb/mods-config/preprocess/hints' + # and the 'raddb/mods-config/preprocess/huntgroups' files. + preprocess + + # If you intend to use CUI and you require that the Operator-Name + # be set for CUI generation and you want to generate CUI also + # for your local clients then uncomment the operator-name + # below and set the operator-name for your clients in clients.conf +# operator-name + + # + # If you want to generate CUI for some clients that do not + # send proper CUI requests, then uncomment the + # cui below and set "add_cui = yes" for these clients in clients.conf +# cui + + # + # If you want to have a log of authentication requests, + # un-comment the following line. +# auth_log + + # + # The chap module will set 'Auth-Type := CHAP' if we are + # handling a CHAP request and Auth-Type has not already been set + chap + + # + # If the users are logging in with an MS-CHAP-Challenge + # attribute for authentication, the mschap module will find + # the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP' + # to the request, which will cause the server to then use + # the mschap module for authentication. + mschap + + # + # If you have a Cisco SIP server authenticating against + # FreeRADIUS, uncomment the following line, and the 'digest' + # line in the 'authenticate' section. + digest + + # + # The WiMAX specification says that the Calling-Station-Id + # is 6 octets of the MAC. This definition conflicts with + # RFC 3580, and all common RADIUS practices. If you are using + # old style WiMAX (non LTE) the un-commenting the "wimax" module + # here means that it will fix the Calling-Station-Id attribute to + # the normal format as specified in RFC 3580 Section 3.21. + # + # If you are using WiMAX 2.1 (LTE) then un-commenting will allow + # the module to handle SQN resyncronisation. Prior to calling the + # module it is necessary to populate the following attributes + # with the relevant keys: + # control:WiMAX-SIM-Ki + # control:WiMAX-SIM-OPc + # + # If WiMAX-Re-synchronization-Info is found in the request then + # the module will attempt to extract SQN and store it in + # control:WiMAX-SIM-SQN. Also a copy of RAND is extracted to + # control:WiMAX-SIM-RAND. + # + # If the SIM cannot be authenticated using Ki and OPc then reject + # will be returned. +# wimax + + # + # Look for IPASS style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. +# IPASS + + # + # Look for realms in user@domain format + suffix +# ntdomain + + # + # This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP + # authentication. + # + # It also sets the EAP-Type attribute in the request + # attribute list to the EAP type from the packet. + # + # The EAP module returns "ok" or "updated" if it is not yet ready + # to authenticate the user. The configuration below checks for + # "ok", and stops processing the "authorize" section if so. + # + # Any LDAP and/or SQL servers will not be queried for the + # initial set of packets that go back and forth to set up + # TTLS or PEAP. + # + # The "updated" check is commented out for compatibility with + # previous versions of this configuration, but you may wish to + # uncomment it as well; this will further reduce the number of + # LDAP and/or SQL queries for TTLS or PEAP. + # + eap { + ok = return +# updated = return + } + + # Pull crypt'd passwords from /etc/passwd or /etc/shadow, using the system API's to get the password. If you want + # to read /etc/passwd or /etc/shadow directly, see the mods-available/passwd module. +# unix + + # Read the 'users' file. In v3, this is located in raddb/mods-config/files/authorize + files + + # Look in an SQL database. The schema of the database is meant to mirror the "users" file. + # + # See "Authorization Queries" in mods-available/sql + -sql + + # If you are using /etc/smbpasswd, and are also doing mschap authentication, the un-comment this line, and configure the 'smbpasswd' module. +# smbpasswd + + # The ldap module reads passwords from the LDAP database. + -ldap + + # Enforce daily limits on time spent logged in. +# daily + + # + expiration + logintime + + # + # If no other module has claimed responsibility for + # authentication, then try to use PAP. This allows the + # other modules listed above to add a "known good" password + # to the request, and to do nothing else. The PAP module + # will then see that password, and use it to do PAP + # authentication. + # + # This module should be listed last, so that the other modules + # get a chance to set Auth-Type for themselves. + # + pap + + # If "status_server = yes", then Status-Server messages are passed through the following section, and ONLY the following section. + # This permits you to do DB queries, for example. If the modules listed here return "fail", then NO response is sent. +# Autz-Type Status-Server { +# +# } + + # + # RADIUS/TLS (or RadSec) connections are processed through + # this section. See sites-available/tls, and the configuration + # item "check_client_connections" for more information. + # + # The request contains TLS client certificate attributes, + # and nothing else. The debug output will print which + # attributes are available on your system. + # + # If the section returns "ok" or "updated", then the + # connection is accepted. Otherwise the connection is + # terminated. + # + Autz-Type New-TLS-Connection { + ok + } +} + + +# Authentication. +# +# +# This section lists which modules are available for authentication. +# Note that it does NOT mean 'try each module in order'. It means +# that a module from the 'authorize' section adds a configuration +# attribute 'Auth-Type := FOO'. That authentication type is then +# used to pick the appropriate module from the list below. +# + +# In general, you SHOULD NOT set the Auth-Type attribute. The server +# will figure it out on its own, and will do the right thing. The +# most common side effect of erroneously setting the Auth-Type +# attribute is that one authentication method will work, but the +# others will not. +# +# The common reasons to set the Auth-Type attribute by hand +# is to either forcibly reject the user (Auth-Type := Reject), +# or to or forcibly accept the user (Auth-Type := Accept). +# +# Note that Auth-Type := Accept will NOT work with EAP. +# +# Please do not put "unlang" configurations into the "authenticate" +# section. Put them in the "post-auth" section instead. That's what +# the post-auth section is for. +# +authenticate { + # PAP authentication, when a back-end database listed in the 'authorize' section supplies a password. The password can be clear-text, or encrypted. + Auth-Type PAP { + pap + } + + # Most people want CHAP authentication + # A back-end database listed in the 'authorize' section MUST supply a CLEAR TEXT password. Encrypted passwords won't work. + Auth-Type CHAP { + chap + } + + # MSCHAP authentication. + Auth-Type MS-CHAP { + mschap + } + + # For old names, too. + mschap + + # If you have a Cisco SIP server authenticating against FreeRADIUS, uncomment the following line, and the 'digest' line in the 'authorize' section. + digest + + # Pluggable Authentication Modules. +# pam + + # Uncomment it if you want to use ldap for authentication + # + # Note that this means "check plain-text password against + # the ldap database", which means that EAP won't work, + # as it does not supply a plain-text password. + # + # We do NOT recommend using this. LDAP servers are databases. + # They are NOT authentication servers. FreeRADIUS is an + # authentication server, and knows what to do with authentication. + # LDAP servers do not. + # +# Auth-Type LDAP { +# ldap +# } + + # + # Allow EAP authentication. + eap + + # + # The older configurations sent a number of attributes in + # Access-Challenge packets, which wasn't strictly correct. + # If you want to filter out these attributes, uncomment + # the following lines. + # +# Auth-Type eap { +# eap { +# handled = 1 +# } +# if (handled && (Response-Packet-Type == Access-Challenge)) { +# attr_filter.access_challenge.post-auth +# handled # override the "updated" code from attr_filter +# } +# } +} + + +# Pre-accounting. Decide which accounting type to use. +# +preacct { + preprocess + + # Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets into a single 64bit counter Acct-[Input|Output]-Octets64. +# acct_counters64 + + # Session start times are *implied* in RADIUS. + # The NAS never sends a "start time". Instead, it sends a start packet, *possibly* with an Acct-Delay-Time. + # The server is supposed to conclude that the start time was "Acct-Delay-Time" seconds in the past. + # + # The code below creates an explicit start time, which can then be used in other modules. It will be *mostly* correct. + # Any errors are due to the 1-second resolution of RADIUS, and the possibility that the time on the NAS may be off. + # + # The start time is: NOW - delay - session_length + +# update request { +# &FreeRADIUS-Acct-Session-Start-Time = "%{expr: %l - %{%{Acct-Session-Time}:-0} - %{%{Acct-Delay-Time}:-0}}" +# } + + # Ensure that we have a semi-unique identifier for every request, and many NAS boxes are broken. + acct_unique + + # Look for IPASS-style 'realm/', and if not found, look for '@realm', and decide whether or not to proxy, based on that. + # + # Accounting requests are generally proxied to the same + # home server as authentication requests. +# IPASS + suffix +# ntdomain + + # Read the 'acct_users' file + files +} + +# +# Accounting. Log the accounting data. +# +accounting { + # Update accounting packet by adding the CUI attribute recorded from the corresponding Access-Accept use it only if your NAS boxes do not support CUI themselves +# cui + # + # Create a 'detail'ed log of the packets. + # Note that accounting requests which are proxied are also logged in the detail file. + detail +# daily + + # Update the wtmp file + # + # If you don't use "radlast", you can delete this line. + unix + + # For Simultaneous-Use tracking. + # Due to packet losses in the network, the data here may be incorrect. There is little we can do about it. +# radutmp +# sradutmp + + # Return an address to the IP Pool when we see a stop record. + # Ensure that &control:Pool-Name is set to determine which pool of IPs are used. +# sqlippool + + # Log traffic to an SQL database. + # See "Accounting queries" in mods-available/sql + -sql + + # + # If you receive stop packets with zero session length, + # they will NOT be logged in the database. The SQL module + # will print a message (only in debugging mode), and will + # return "noop". + # + # You can ignore these packets by uncommenting the following + # three lines. Otherwise, the server will not respond to the + # accounting request, and the NAS will retransmit. + # +# if (noop) { +# ok +# } + + # Cisco VoIP specific bulk accounting +# pgsql-voip + + # For Exec-Program and Exec-Program-Wait + exec + + # Filter attributes from the accounting response. + attr_filter.accounting_response + + # + # See "Autz-Type Status-Server" for how this works. + # +# Acct-Type Status-Server { +# +# } +} + + +# Session database, used for checking Simultaneous-Use. Either the radutmp rlm_sql module can handle this. +# The rlm_sql module is *much* faster +session { +# radutmp + + # See "Simultaneous Use Checking Queries" in mods-available/sql + sql +} + + +# Post-Authentication +# Once we KNOW that the user has been authenticated, there are +# additional steps we can take. +post-auth { + # + # If you need to have a State attribute, you can + # add it here. e.g. for later CoA-Request with + # State, and Service-Type = Authorize-Only. + # +# if (!&reply:State) { +# update reply { +# State := "0x%{randstr:16h}" +# } +# } + + # + # Reject packets where User-Name != TLS-Client-Cert-Common-Name + # There is no reason for users to lie about their names. + # + # In general, User-Name == EAP Identity == TLS-Client-Cert-Common-Name + # +# verify_tls_client_common_name + + # + # If there is no Stripped-User-Name in the request, AND we have a client cert, + # then create a Stripped-User-Name from the TLS client certificate information. + # + # Note that this policy MUST be edited for your local system! + # We do not know which fields exist in which certificate, as + # there is no standard here. There is no way for us to have + # a default configuration which "just works" everywhere. We + # can only make recommendations. + # + # The Stripped-User-Name is updated so that it is logged in + # the various "username" fields. This logging means that you + # can associate a particular session with a particular client + # certificate. + # +# if (&EAP-Message && !&Stripped-User-Name && &TLS-Client-Cert-Serial) { +# update request { +# &Stripped-User-Name := "%{%{TLS-Client-Cert-Subject-Alt-Name-Email}:-%{%{TLS-Client-Cert-Common-Name}:-%{TLS-Client-Cert-Serial}}}" +# } +# + # + # Create a Class attribute which is a hash of a bunch + # of information which we hope exists. This + # attribute should be echoed back in + # Accounting-Request packets, which will let the + # administrator correlate authentication and + # accounting. + # +# update reply { +# Class += "%{md5:%{Calling-Station-Id}%{Called-Station-Id}%{TLS-Client-Cert-Subject-Alt-Name-Email}%{TLS-Client-Cert-Common-Name}%{TLS-Client-Cert-Serial}%{NAS-IPv6-Address}%{NAS-IP-Address}%{NAS-Identifier}%{NAS-Port}" +# } +# +# } + + # + # For EAP-TTLS and PEAP, add the cached attributes to the reply. + # The "session-state" attributes are automatically cached when + # an Access-Challenge is sent, and automatically retrieved + # when an Access-Request is received. + # + # The session-state attributes are automatically deleted after + # an Access-Reject or Access-Accept is sent. + # + # If both session-state and reply contain a User-Name attribute, remove + # the one in the reply if it is just a copy of the one in the request, so + # we don't end up with two User-Name attributes. + + if (session-state:User-Name && reply:User-Name && request:User-Name && (reply:User-Name == request:User-Name)) { + update reply { + &User-Name !* ANY + } + } + update { + &reply: += &session-state: + } + + # + # Refresh leases when we see a start or alive. Return an address to + # the IP Pool when we see a stop record. + # + # Ensure that &control:Pool-Name is set to determine which + # pool of IPs are used. +# sqlippool + + + # Create the CUI value and add the attribute to Access-Accept. + # Uncomment the line below if *returning* the CUI. +# cui + + # Create empty accounting session to make simultaneous check more robust. See the accounting queries configuration in + # raddb/mods-config/sql/main/*/queries.conf for details. + # + # The "sql_session_start" policy is defined in raddb/policy.d/accounting. See that file for more details. +# sql_session_start + + # + # If you want to have a log of authentication replies, + # un-comment the following line, and enable the + # 'detail reply_log' module. +# reply_log + + # + # After authenticating the user, do another SQL query. + # + # See "Authentication Logging Queries" in mods-available/sql + -sql + + # + # Un-comment the following if you want to modify the user's object + # in LDAP after a successful login. + # +# ldap + + # For Exec-Program and Exec-Program-Wait + exec + + # + # In order to calcualate the various keys for old style WiMAX + # (non LTE) you will need to define the WiMAX NAI, usually via + # + # update request { + # &WiMAX-MN-NAI = "%{User-Name}" + # } + # + # If you want various keys to be calculated, you will need to + # update the reply with "template" values. The module will see + # this, and replace the template values with the correct ones + # taken from the cryptographic calculations. e.g. + # + # update reply { + # &WiMAX-FA-RK-Key = 0x00 + # &WiMAX-MSK = "%{reply:EAP-MSK}" + # } + # + # You may want to delete the MS-MPPE-*-Keys from the reply, + # as some WiMAX clients behave badly when those attributes + # are included. See "raddb/modules/wimax", configuration + # entry "delete_mppe_keys" for more information. + # + # For LTE style WiMAX you need to populate the following with the + # relevant values: + # control:WiMAX-SIM-Ki + # control:WiMAX-SIM-OPc + # control:WiMAX-SIM-AMF + # control:WiMAX-SIM-SQN + # +# wimax + + # If there is a client certificate (EAP-TLS, sometimes PEAP + # and TTLS), then some attributes are filled out after the + # certificate verification has been performed. These fields + # MAY be available during the authentication, or they may be + # available only in the "post-auth" section. + # + # The first set of attributes contains information about the + # issuing certificate which is being used. The second + # contains information about the client certificate (if + # available). +# +# update reply { +# Reply-Message += "%{TLS-Cert-Serial}" +# Reply-Message += "%{TLS-Cert-Expiration}" +# Reply-Message += "%{TLS-Cert-Subject}" +# Reply-Message += "%{TLS-Cert-Issuer}" +# Reply-Message += "%{TLS-Cert-Common-Name}" +# Reply-Message += "%{TLS-Cert-Subject-Alt-Name-Email}" +# +# Reply-Message += "%{TLS-Client-Cert-Serial}" +# Reply-Message += "%{TLS-Client-Cert-Expiration}" +# Reply-Message += "%{TLS-Client-Cert-Subject}" +# Reply-Message += "%{TLS-Client-Cert-Issuer}" +# Reply-Message += "%{TLS-Client-Cert-Common-Name}" +# Reply-Message += "%{TLS-Client-Cert-Subject-Alt-Name-Email}" +# } + + # Insert class attribute (with unique value) into response, + # aids matching auth and acct records, and protects against duplicate + # Acct-Session-Id. Note: Only works if the NAS has implemented + # RFC 2865 behaviour for the class attribute, AND if the NAS + # supports long Class attributes. Many older or cheap NASes + # only support 16-octet Class attributes. +# insert_acct_class + + # MacSEC requires the use of EAP-Key-Name. However, we don't + # want to send it for all EAP sessions. Therefore, the EAP + # modules put required data into the EAP-Session-Id attribute. + # This attribute is never put into a request or reply packet. + # + # Uncomment the next few lines to copy the required data into + # the EAP-Key-Name attribute +# if (&reply:EAP-Session-Id) { +# update reply { +# EAP-Key-Name := &reply:EAP-Session-Id +# } +# } + + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + + # + # Access-Reject packets are sent through the REJECT sub-section of the + # post-auth section. + # + # Add the ldap module name (or instance) if you have set + # 'edir = yes' in the ldap module configuration + # + # The "session-state" attributes are not available here. + # + Post-Auth-Type REJECT { + # log failed authentications in SQL, too. + -sql + attr_filter.access_reject + + # Insert EAP-Failure message if the request was rejected by policy instead of because of an authentication failure + eap + + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + } + + # Filter access challenges. + # + Post-Auth-Type Challenge { +# remove_reply_message_if_eap +# attr_filter.access_challenge.post-auth + } + + # + # The Client-Lost section will be run for a request when + # FreeRADIUS has given up waiting for an end-users client to + # respond. This is most useful for logging EAP sessions where + # the client stopped responding (likely because the + # certificate was not acceptable.) i.e. this is not for + # RADIUS clients, but for end-user systems. + # + # This will only be triggered by new packets arriving, + # and will be run at some point in the future *after* the + # original request has been discarded. + # + # Therefore the *ONLY* attributes that are available here + # are those in the session-state list. If you want data + # to log, make sure it is copied to &session-state: + # before the client stops responding. NONE of the other + # original attributes (request, reply, etc) will be + # available. + # + # This section will only be run if `postauth_client_lost` + # is enabled in the main configuration in `radiusd.conf`. + # + # Note that there are MANY reasons why an end users system + # might not respond: + # + # * it could not get the packet due to firewall issues + # * it could not get the packet due to a lossy network + # * the users system might not like the servers cert + # * the users system might not like something else... + # + # In some cases, the client is helpful enough to send us a + # TLS Alert message, saying what it doesn't like about the + # certificate. In other cases, no such message is available. + # + # All that we can know on the FreeRADIUS side is that we sent + # an Access-Challenge, and the client never sent anything + # else. The reasons WHY this happens are buried inside of + # the logs on the client system. No amount of looking at the + # FreeRADIUS logs, or poking the FreeRADIUS configuration + # will tell you why the client gave up. The answers are in + # the logs on the client side. And no, the FreeRADIUS team + # didn't write the client, so we don't know where those logs + # are, or how to get at them. + # + # Information about the TLS state changes is in the + # &session-state:TLS-Session-Information attribute. + # + Post-Auth-Type Client-Lost { + # + # Debug ALL of the TLS state changes done during the + # EAP negotiation. + # +# %{debug_attr:&session-state:TLS-Session-Information[*]} + + # + # Debug the LAST TLS state change done during the EAP + # negotiation. For errors, this is usually a TLS + # alert from the client saying something like + # "unknown CA". + # +# %{debug_attr:&session-state:TLS-Session-Information[n]} + + # + # Debug the last module failure message. This may be + # useful, or it may refer to a server-side failure + # which did not cause the client to stop talking to the server. + # +# %{debug_attr:&session-state:Module-Failure-Message} + } + + # + # If the client sends EAP-Key-Name in the request, + # then echo the real value back in the reply. + # + if (EAP-Key-Name && &reply:EAP-Session-Id) { + update reply { + &EAP-Key-Name := &reply:EAP-Session-Id + } + } +} + +# +# When the server decides to proxy a request to a home server, +# the proxied request is first passed through the pre-proxy +# stage. This stage can re-write the request, or decide to +# cancel the proxy. +# +# Only a few modules currently have this method. +# +pre-proxy { + # Before proxing the request add an Operator-Name attribute identifying + # if the operator-name is found for this client. + # No need to uncomment this if you have already enabled this in + # the authorize section. +# operator-name + + # The client requests the CUI by sending a CUI attribute + # containing one zero byte. + # Uncomment the line below if *requesting* the CUI. +# cui + + # Uncomment the following line if you want to change attributes + # as defined in the preproxy_users file. +# files + + # Uncomment the following line if you want to filter requests + # sent to remote servers based on the rules defined in the + # 'attrs.pre-proxy' file. +# attr_filter.pre-proxy + + # If you want to have a log of packets proxied to a home + # server, un-comment the following line, and the + # 'detail pre_proxy_log' section, above. +# pre_proxy_log +} + +# +# When the server receives a reply to a request it proxied +# to a home server, the request may be massaged here, in the +# post-proxy stage. +# +post-proxy { + + # If you want to have a log of replies from a home server, + # un-comment the following line, and the 'detail post_proxy_log' + # section, above. +# post_proxy_log + + # Uncomment the following line if you want to filter replies from + # remote proxies based on the rules defined in the 'attrs' file. +# attr_filter.post-proxy + + # + # If you are proxying LEAP, you MUST configure the EAP + # module, and you MUST list it here, in the post-proxy + # stage. + # + # You MUST also use the 'nostrip' option in the 'realm' + # configuration. Otherwise, the User-Name attribute + # in the proxied request will not match the user name + # hidden inside of the EAP packet, and the end server will + # reject the EAP request. + # + eap + + # + # If the server tries to proxy a request and fails, then the + # request is processed through the modules in this section. + # + # The main use of this section is to permit robust proxying + # of accounting packets. The server can be configured to + # proxy accounting packets as part of normal processing. + # Then, if the home server goes down, accounting packets can + # be logged to a local "detail" file, for processing with + # radrelay. When the home server comes back up, radrelay + # will read the detail file, and send the packets to the + # home server. + # + # See the "mods-available/detail.example.com" file for more + # details on writing a detail file specifically for one + # destination. + # + # See the "sites-available/robust-proxy-accounting" virtual + # server for more details on reading this "detail" file. + # + # With this configuration, the server always responds to + # Accounting-Requests from the NAS, but only writes + # accounting packets to disk if the home server is down. + # +# Post-Proxy-Type Fail-Accounting { +# detail.example.com +# } +} +} \ No newline at end of file diff --git a/files/sites-available/dhcp b/files/sites-available/dhcp new file mode 100644 index 0000000..f8f8416 --- /dev/null +++ b/files/sites-available/dhcp @@ -0,0 +1,595 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server that handles DHCP. +# +# See raddb/mods-available/dhcp_sqlippool for the IP Pool configuration. +# +# See raddb/policy.d/dhcp_sqlippool for the "glue" code that allows +# the RADIUS based "sqlippool" module to be used for DHCP. +# +# See raddb/mods-config/sql/ippool/ for the schemas. +# +# See raddb/sites-available/dhcp for instructions on how to configure +# the DHCP server. +# +# $Id$ +# +###################################################################### + +# +# The DHCP functionality goes into a virtual server. +# +server dhcp { + +# Define a DHCP socket. +# +# The default port below is 6700, so you don't break your network. +# If you want it to do real DHCP, change this to 67, and good luck! +# +# You can also bind the DHCP socket to an interface. +# See below, and raddb/radiusd.conf for examples. +# +# This lets you run *one* DHCP server instance and have it listen on +# multiple interfaces, each with a separate policy. +# +# If you have multiple interfaces, it is a good idea to bind the +# listen section to an interface. You will also need one listen +# section per interface. +# +# FreeBSD does *not* support binding sockets to interfaces. Therefore, +# if you have multiple interfaces, broadcasts may go out of the wrong +# one, or even all interfaces. The solution is to use the "setfib" command. +# If you have a network "10.10.0/24" on LAN1, you will need to do: +# +# Pick any IP on the 10.10.0/24 network +# $ setfib 1 route add default 10.10.0.1 +# +# Edit /etc/rc.local, and add a line: +# setfib 1 /path/to/radiusd +# +# The kern must be built with the following options: +# options ROUTETABLES=2 +# or any value larger than 2. +# +# The other only solution is to update FreeRADIUS to use BPF sockets. +# +listen { + # This is a dhcp socket. + type = dhcp + + # IP address to listen on. Will usually be the IP of the + # interface, or 0.0.0.0 + ipaddr = 0.0.0.0 + + # source IP address for unicast packets sent by the + # DHCP server. + # + # The source IP for unicast packets is chosen from the first + # one of the following items which returns a valid IP + # address: + # + # src_ipaddr + # ipaddr + # reply:DHCP-Server-IP-Address + # reply:DHCP-DHCP-Server-Identifier + # + src_ipaddr = 127.0.0.1 + + # The port should be 67 for a production network. Don't set + # it to 67 on a production network unless you really know + # what you're doing. Even if nothing is configured below, the + # server may still NAK legitimate responses from clients. + port = 6700 + + # Interface name we are listening on. See comments above. +# interface = lo0 + + # The DHCP server defaults to allowing broadcast packets. + # Set this to "no" only when the server receives *all* packets + # from a relay agent. i.e. when *no* clients are on the same + # LAN as the DHCP server. + # + # It's set to "no" here for testing. It will usually want to + # be "yes" in production, unless you are only dealing with + # relayed packets. + broadcast = no + + # On Linux if you're running the server as non-root, you + # will need to do: + # + # setcap cap_net_admin,cap_net_bind_service=eip /path/to/radiusd + # + # This will allow the server to set ARP table entries + # for newly allocated IPs, when run as the "radius" user. + # + # The above "setcap" command adds the capability to the program, + # usually so long as it is run by the "radius" user. Which means + # (oddly enough) that it no longer works when run as root! + # + # When running the server as root in debug mode, you can use: + # + # capsh --caps="cap_setpcap,cap_setuid,cap_setgid,cap_net_admin,cap_net_bind_service+eip" --keep=1 --user=radius --addamb=cap_net_admin,cap_net_bind_service -- -c "/path/to/radiusd -X" + # + # Or, simply "sudo" or "su" to the "radius" user, and then run + # the server in debug mode. + + # De-duplicate DHCP packets. If clients don't receive + # a reply within their timeout, most will re-transmit. + # A reply to either packet will satisfy, so de-duplicating + # helps manage load on a busy server + performance { + skip_duplicate_checks = no + } +} + +# Packets received on the socket will be processed through one +# of the following sections, named after the DHCP packet type. +# See dictionary.dhcp for the packet types. + +# Return packets will be sent to, in preference order: +# DHCP-Gateway-IP-Address +# DHCP-Client-IP-Address +# DHCP-Your-IP-Address +# At least one of these attributes should be set at the end of each +# section for a response to be sent. + +# An internal attribute of DHCP-Network-Subnet is set to provide +# a basis for determining the network that a client belongs to. This +# is a hierarchical assignment based on: +# +# - DHCP-Relay-Link-Selection +# - DHCP-Subnet-Selection-Option +# - DHCP-Gateway-IP-Address +# - DHCP-Client-IP-Address +# +# Except for cases where all IP allocation is performed using a mapping from +# the device MAC address to a fixed IP address the DHCP configuration will +# involve the use of one or more pools. +# +# Each pool should be composed of a set of equally valid IP addresses for the +# devices designated as users of the pool. During IP allocation the choice of +# pool is driven by setting the Pool-Name attribute which may either be +# specified directly or chosen (usually with the help of the dhcp_network +# module) based on the initial value of DHCP-Network-Subnet. +# +# DHCP-Network-Subnet indicates the network from which the request is +# originating. In cases where the originating network alone is insufficent to +# define the required IP allocated policy, DHCP-Network-Subnet may be +# overridden to force the selection of a particular pool. +# +# IP addresses belonging to a single pool that is designated for a Layer 2 +# network containing multiple subnets (a "shared-network" or "multinet" +# configuration as defined by some other DHCP servers), will by definition be +# members of distinct subnets that require their own DHCP reply parameters. In +# this case the dhcp_subnet policy can be used to set the correct +# DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address options +# based on the allocated IP. + +dhcp DHCP-Discover { + + # The DHCP Server Identifier is set here since is returned in OFFERs + update control { + &DHCP-DHCP-Server-Identifier = 192.0.2.2 + } + + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # If clients need to be assigned to a particular network based on + # an attribute in the packet rather than the calculated + # DHCP-Network-Subnet described above, then call a policy + # (defined in policy.d/dhcp) to perform the override + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Do a simple mapping of MAC to assigned IP. + # + # See below for the definition of the "mac2ip" + # module. + # + #mac2ip + + # Or, allocate IPs from the DHCP pool in SQL. You may need to + # set the pool name here if you haven't set it elsewhere. + #update control { + # &Pool-Name := "local" + #} + #dhcp_sqlippool + + # If the IP address was not allocated, do something else. + # You could call a Perl, Python, or Java script here. + #if (notfound) { + # ... + #} + + # "Shared-networks" may have multiple IP subnets co-existing in a + # single Layer 2 network. If the pool for the network contains + # addresses from more that one subnet then the setting subnet-specific + # DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address + # parameters must be performed after the allocation of the IP address. + # + # Set any subnet-specific parameters using this policy. + # + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this. + # + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # As an alternative or complement to configuration files based lookup + # for options data you can instead use an SQL database. Example + # configuration is found in dhcp_policy_sql in policy.d/dhcp which + # will need to be adapted to your requirements. + #dhcp_policy_sql + + # Set the type of packet to send in reply. + # + # The server will look at the DHCP-Message-Type attribute to + # determine which type of packet to send in reply. Common + # values would be DHCP-Offer, DHCP-Ack or DHCP-NAK. See + # dictionary.dhcp for all the possible values. + # + # DHCP-Do-Not-Respond can be used to tell the server to not + # respond. + # + # In the event that DHCP-Message-Type is not set then the + # server will fall back to determining the type of reply + # based on the rcode of this section. + # + #update reply { + # DHCP-Message-Type = DHCP-Offer + #} + # + # If DHCP-Message-Type is not set, returning "ok" or + # "updated" from this section will respond with a DHCP-Offer + # message. + # + # Other rcodes will tell the server to not return any response. + # + #ok +} + +dhcp DHCP-Request { + + # You must set the DHCP Server Identifier here since this is returned + # in ACKs and is used to determine whether a request containing a + # "server-ip" field is intended for this server + update control { + &DHCP-DHCP-Server-Identifier = 192.0.2.2 + } + + # If the request is not for this server then silently discard it + if (&request:DHCP-DHCP-Server-Identifier && \ + &request:DHCP-DHCP-Server-Identifier != &control:DHCP-DHCP-Server-Identifier) { + do_not_respond + } + + # Response packet type. See DHCP-Discover section above. + #update reply { + # &DHCP-Message-Type = DHCP-Ack + #} + + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Do a simple mapping of MAC to assigned IP. + # + # See below for the definition of the "mac2ip" + # module. + # + #mac2ip + + # Or, allocate IPs from the DHCP pool in SQL. You may need to + # set the pool name here if you haven't set it elsewhere. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_request + + # If the IP was not allocated, do something else. + # You could call a Perl, Python, or Java script here. + #if (notfound) { + # ... + #} + + # "Shared-networks" may have multiple IP subnets co-existing in a + # single Layer 2 network. If the pool for the network contains + # addresses from more that one subnet then the setting subnet-specific + # DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address + # parameters must be performed after the allocation of the IP address. + # + # Set any subnet-specific parameters using this policy. + # + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # As an alternative or complement to configuration files based lookup + # for options data you can instead use an SQL database. Example + # configuration is found in dhcp_policy_sql in policy.d/dhcp which + # will need to be adapted to your requirements. + #dhcp_policy_sql + + # If DHCP-Message-Type is not set, returning "ok" or + # "updated" from this section will respond with a DHCP-Ack + # packet. + # + # "handled" will not return a packet, all other rcodes will + # send back a DHCP-NAK. + # + #ok +} + +# +# Other DHCP packet types +# +# There should be a separate section for each DHCP message type. +# By default this configuration will ignore them all. Any packet type +# not defined here will be responded to with a DHCP-NAK. + +dhcp DHCP-Decline { + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple networks use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Use a policy that set options from data stored in an SQL database + #dhcp_policy_sql + + # If using IPs from a DHCP pool in SQL then you may need to set the + # pool name here if you haven't set it elsewhere and release the IP. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_decline + + update reply { + &DHCP-Message-Type = DHCP-Do-Not-Respond + } + reject +} + +# +# A dummy config for Inform packets - this should match the +# options set in the Request section above, except Inform replies +# must not set Your-IP-Address or IP-Address-Lease-Time +# +dhcp DHCP-Inform { + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and network options + # For multiple networks use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Use a policy with calls a "files" module of the same name to lookup + # subnet options + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this + # Options are set in mods-config/files/dhcp + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # Use a policy that set options from data stored in an SQL database + #dhcp_policy_sql + + ok +} + +# +# For Windows 7 boxes +# +#dhcp DHCP-Inform { +# update reply { +# Packet-Dst-Port = 67 +# DHCP-Message-Type = DHCP-ACK +# DHCP-DHCP-Server-Identifier = "%{Packet-Dst-IP-Address}" +# DHCP-Site-specific-28 = 0x0a00 +# } +# ok +#} + +dhcp DHCP-Release { + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # If using IPs from a DHCP pool in SQL then you may need to set the + # pool name here if you haven't set it elsewhere and release the IP. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_release + + update reply { + &DHCP-Message-Type = DHCP-Do-Not-Respond + } + reject +} + + +dhcp DHCP-Lease-Query { + # The thing being queried for is implicit + # in the packets. + + # has MAC, asking for IP, etc. + if (&DHCP-Client-Hardware-Address) { + # look up MAC in database + } + + # has IP, asking for MAC, etc. + elsif (&DHCP-Your-IP-Address) { + # look up IP in database + } + + # has host name, asking for IP, MAC, etc. + elsif (&DHCP-Client-Identifier) { + # look up identifier in database + } + else { + update reply { + &DHCP-Message-Type = DHCP-Lease-Unknown + } + + ok + + # stop processing + return + } + + # + # We presume that the database lookup returns "notfound" + # if it can't find anything. + # + if (notfound) { + update reply { + &DHCP-Message-Type = DHCP-Lease-Unknown + } + ok + return + } + + # + # Add more logic here. Is the lease inactive? + # If so, respond with DHCP-Lease-Unassigned. + # + # Otherwise, respond with DHCP-Lease-Active + # + + # + # Also be sure to return ALL information about + # the lease. + # + + # + # The reply types are: + # + # DHCP-Lease-Unknown + # DHCP-Lease-Active + # DHCP-Lease-Unassigned + # + update reply { + &DHCP-Message-Type = DHCP-Lease-Unassigned + } + +} + +} + +###################################################################### +# +# This next section is a sample configuration for the "passwd" +# module, that reads flat-text files. It should go into +# radiusd.conf, in the "modules" section. +# +# The file is in the format , +# +# 00:01:02:03:04:05,192.0.2.100 +# 01:01:02:03:04:05,192.0.2.101 +# 02:01:02:03:04:05,192.0.2.102 +# +# This lets you perform simple static IP assignment. +# +# There is a preconfigured "mac2ip" module setup in +# mods-available/mac2ip. To use it do: +# +# # cd raddb/ +# # ln -s ../mods-available/mac2ip mods-enabled/mac2ip +# # mkdir mods-config/passwd +# +# Then create the file mods-config/passwd/mac2ip with the above +# format. +# +###################################################################### + + +# This is an example only - see mods-available/mac2ip instead; do +# not uncomment these lines here. +# +#passwd mac2ip { +# filename = ${confdir}/mac2ip +# format = "*DHCP-Client-Hardware-Address:=DHCP-Your-IP-Address" +# delimiter = "," +#} \ No newline at end of file diff --git a/files/sites-available/inner-tunnel b/files/sites-available/inner-tunnel new file mode 100644 index 0000000..918cd35 --- /dev/null +++ b/files/sites-available/inner-tunnel @@ -0,0 +1,126 @@ +###################################################################### +# +# This is a virtual server that handles *only* inner tunnel +# requests for EAP-TTLS and PEAP types. +# +###################################################################### + +server inner-tunnel { + + listen { + ipaddr = 127.0.0.1 + port = 18120 + type = auth + } + + authorize { + filter_username + # filter_inner_identity + chap + mschap + # unix + # IPASS + suffix + # ntdomain + + update control { + &Proxy-To-Realm := LOCAL + } + + eap { + ok = return + } + + files + -sql + # smbpasswd + -ldap + # daily + expiration + logintime + pap + } + + authenticate { + Auth-Type PAP { + pap + } + + Auth-Type CHAP { + chap + } + + Auth-Type MS-CHAP { + mschap + } + + mschap + # pam + + # Auth-Type LDAP { + # ldap + # } + + eap + } + + session { + radutmp + # sql + } + + # Post-Authentication + post-auth { + # cui-inner + + # update outer.session-state { + # User-Name := &User-Name + # } + + # reply_log + -sql + # ldap + # moonshot_host_tid + # moonshot_realm_tid + # moonshot_coi_tid + + if (0) { + update reply { + User-Name !* ANY + Message-Authenticator !* ANY + EAP-Message !* ANY + Proxy-State !* ANY + MS-MPPE-Encryption-Types !* ANY + MS-MPPE-Encryption-Policy !* ANY + MS-MPPE-Send-Key !* ANY + MS-MPPE-Recv-Key !* ANY + } + + update { + &outer.session-state: += &reply: + } + } + + Post-Auth-Type REJECT { + -sql + attr_filter.access_reject + + update outer.session-state { + &Module-Failure-Message := &request:Module-Failure-Message + } + } + } + + pre-proxy { + # files + # attr_filter.pre-proxy + # pre_proxy_log + } + + post-proxy { + # post_proxy_log + # attr_filter.post-proxy + eap + } + +} # inner-tunnel server block \ No newline at end of file diff --git a/files/sites-available/status b/files/sites-available/status new file mode 100644 index 0000000..74c322d --- /dev/null +++ b/files/sites-available/status @@ -0,0 +1,126 @@ +# -*- text -*- +###################################################################### +# +# A virtual server to handle ONLY Status-Server packets. +# +# Server statistics can be queried with a properly formatted +# Status-Server request. See dictionary.freeradius for comments. +# +# If radiusd.conf has "status_server = yes", then any client +# will be able to send a Status-Server packet to any port +# (listen section type "auth", "acct", or "status"), and the +# server will respond. +# +# If radiusd.conf has "status_server = no", then the server will +# ignore Status-Server packets to "auth" and "acct" ports. It +# will respond only if the Status-Server packet is sent to a +# "status" port. +# +# The server statistics are available ONLY on socket of type +# "status". Queries for statistics sent to any other port +# are ignored. +# +# Similarly, a socket of type "status" will not process +# authentication or accounting packets. This is for security. +# +# $Id: e7d4346310b837d56bffe4c991b4e5680742ebc0 $ +# +###################################################################### + +server status { + listen { + # ONLY Status-Server is allowed to this port. + # ALL other packets are ignored. + type = status + + ipaddr = $ENV{FREERADIUS_SITES_STATUS_LISTEN} + port = $ENV{FREERADIUS_SITES_STATUS_PORT} + } + + # + # We recommend that you list ONLY management clients here. + # i.e. NOT your NASes or Access Points, and for an ISP, + # DEFINITELY not any RADIUS servers that are proxying packets + # to you. + # + # If you do NOT list a client here, then any client that is + # globally defined (i.e. all of them) will be able to query + # these statistics. + # + # Do you really want your partners seeing the internal details + # of what your RADIUS server is doing? + # + client admin { + ipaddr = 127.0.0.1 + secret = $ENV{FREERADIUS_SITES_STATUS_SECRET} + } + + # Simple authorize section. The "Autz-Type Status-Server" + # section will work here, too. See "raddb/sites-available/default". + authorize { + ok + + # respond to the Status-Server request. + Autz-Type Status-Server { + ok + } + } +} + +# Statistics can be queried via a number of methods: +# +# All packets received/sent by the server (1 = auth, 2 = acct) +# FreeRADIUS-Statistics-Type = 3 +# +# All packets proxied by the server (4 = proxy-auth, 8 = proxy-acct) +# FreeRADIUS-Statistics-Type = 12 +# +# All packets sent && received: +# FreeRADIUS-Statistics-Type = 15 +# +# Internal server statistics: +# FreeRADIUS-Statistics-Type = 16 +# +# All packets for a particular client (globally defined) +# FreeRADIUS-Statistics-Type = 35 +# FreeRADIUS-Stats-Client-IP-Address = 192.0.2.1 +# +# All packets for a client attached to a "listen" ip/port +# FreeRADIUS-Statistics-Type = 35 +# FreeRADIUS-Stats-Client-IP-Address = 192.0.2.1 +# FreeRADIUS-Stats-Server-IP-Address = 127.0.0.1 +# FreeRADIUS-Stats-Server-Port = 1812 +# +# All packets for a "listen" IP/port +# FreeRADIUS-Statistics-Type = 67 +# FreeRADIUS-Stats-Server-IP-Address = 127.0.0.1 +# FreeRADIUS-Stats-Server-Port = 1812 +# +# All packets for a home server IP / port +# FreeRADIUS-Statistics-Type = 131 +# FreeRADIUS-Stats-Server-IP-Address = 192.0.2.2 +# FreeRADIUS-Stats-Server-Port = 1812 + +# +# You can also get exponentially weighted moving averages of +# response times (in usec) of home servers. Just set the config +# item "historic_average_window" in a home_server section. +# +# By default it is zero (don't calculate it). Useful values +# are between 100, and 10,000. The server will calculate and +# remember the moving average for this window, and for 10 times +# that window. +# + +# +# Some of this could have been simplified. e.g. the proxy-auth and +# proxy-acct bits aren't completely necessary. But using them permits +# the server to be queried for ALL inbound && outbound packets at once. +# This gives a good snapshot of what the server is doing. +# +# Due to internal limitations, the statistics might not be exactly up +# to date. Do not expect all of the numbers to add up perfectly. +# The Status-Server packets are also counted in the total requests && +# responses. The responses are counted only AFTER the response has +# been sent. +# diff --git a/files/sites-available/tls b/files/sites-available/tls new file mode 100644 index 0000000..28eb615 --- /dev/null +++ b/files/sites-available/tls @@ -0,0 +1,603 @@ +###################################################################### +# +# RADIUS over TLS (radsec) +# +# When a new client connects, the various TLS parameters for the +# connection are available as dynamic expansions, e.g. +# +# %{listen:TLS-Client-Cert-Common-Name} +# +# Along with other TLS-Client-Cert-... attributes. +# These expansions will only exist if the relevant fields +# are in the client certificate. Read the debug output to see +# which fields are available. Look for output like the following: +# +# (0) TLS - Creating attributes from certificate OIDs +# (0) TLS-Client-Cert-Subject-Alt-Name-Dns := "one.example.org" +# (0) TLS-Client-Cert-Subject-Alt-Name-Dns := "two.example.org" +# ... +# +# It is also possible to distinguish between connections which have +# TLS enables, and ones which do not. The expansion: +# +# %{listen:tls} +# +# Will return "yes" if the connection has TLS enabled. It will +# return "no" if TLS is not enabled for a particular listen section. +# +# A number of TLS-Client-Cert-.. attributes holds X509v3 extensions +# data, attributes named the way OpenSSL names them. It is possible +# to extract data for an extension not known to OpenSSL by defining +# a custom string attribute which contains extension OID in it's +# name after 'TLS-Client-Cert-' prefix. E.g.: +# +# ATTRIBUTE TLS-Client-Cert-1.3.6.1.4.1.311.21.7 3002 string +# +# which will yield something simmilar to: +# +# (0) eap_tls: TLS - Creating attributes from certificate OIDs +# (0) eap_tls: TLS-Client-Cert-1.3.6.1.4.1.311.21.7 += "0x302e06" +# ... +# +###################################################################### + +listen { + + # ipaddr = $ENV{FREERADIUS_SITES_TLS_LISTEN} + ipaddr = * + port = $ENV{FREERADIUS_SITES_TLS_PORT} + + # + # TCP and TLS sockets can accept Access-Request and Accounting-Request on the same socket. + # + # auth = only Access-Request + # acct = only Accounting-Request + # auth+acct = both + # coa = only CoA / Disconnect requests + # + type = auth+acct + + # For now, only TCP transport is allowed. + proto = tcp + + # Send packets to the default virtual server + virtual_server = default + + clients = radsec + + # Use the haproxy "PROXY protocol". + # + # This configuration allows for many FreeRADIUS servers to be behind a haproxy server. The "PROXY protocol" allows haproxy to send the actual client IP to FreeRADIUS. + # + # This will work ONLY for RadSec (TLS). Both the haproxy AND the RadSec client MUST be listed as allowed RADIUS clients. + # + # haproxy needs to have "send-proxy" configured for this server. Health checks should be turned off, as haproxy does not support RADIUS health checks. + # + # The main use of this feature is for scalability. There is no longer any need to have a RADIUS proxy as a load balancer. + # haproxy is fast, stable, and supports dynamic reloads! + # + # The only problem is that many RADIUS clients do not support RadSec. That situation will hopefully change over time. + # +# proxy_protocol = no + + # When this is set to "yes", new TLS connections are processed through a section called + # + # Autz-Type New-TLS-Connection { + # ... + # } + # + # The request contains TLS client certificate attributes, + # and nothing else. The debug output will print which + # attributes are available on your system. + # + # If the section returns "ok" or "updated", then the + # connection is accepted. Otherwise the connection is + # terminated. + # +# check_client_connections = yes + + # + # Connection limiting for sockets with "proto = tcp". + # + limit { + # Limit the number of simultaneous TCP connections to the socket + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # The lifetime, in seconds, of a TCP connection. After this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # The idle timeout, in seconds, of a TCP connection. If no packets have been received over the connection for this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + # We STRONGLY RECOMMEND that you set an idle timeout. + # + idle_timeout = 30 + } + + # This is *exactly* the same configuration as used by the EAP-TLS + # module. It's OK for testing, but for production use it's a good + # idea to use different server certificates for EAP and for RADIUS + # transport. + # + # If you want only one TLS configuration for multiple sockets, + # then we suggest putting "tls { ...}" into radiusd.conf. + # The subsection below can then be changed into a reference: + # + # tls = ${tls} + # + # Which means "the tls sub-section is not here, but instead is in + # the top-level section called 'tls'". + # + # If you have multiple tls configurations, you can put them into + # sub-sections of a top-level "tls" section. There's no need to + # call them all "tls". You can then use: + # + # tls = ${tls.site1} + # + # to refer to the "site1" sub-section of the "tls" section. + # + tls { + private_key_password = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_PASSWORD} + private_key_file = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_FILE} + + # Accept an expired Certificate Revocation List + # allow_expired_crl = no + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below MUST include not + # only the server certificate, but ALSO all + # of the CA certificates used to sign the + # server certificate. + certificate_file = $ENV{FREERADIUS_SITES_TLS_CERTIFICATE_FILE} + + + # Trusted Root CA list + # + # ALL of the CA's in this list will be trusted to issue client certificates for authentication. + # + # In general, you should use self-signed certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain *one* CA certificate. + # + # This parameter is used only for EAP-TLS, when you issue client certificates. If you do not use client certificates, and you do not want + # to permit EAP-TLS authentication, then delete this configuration item. + ca_file = $ENV{FREERADIUS_SITES_TLS_CA_FILE} + + # For DH cipher suites to work, you have to run OpenSSL to create the DH file first: + # + # openssl dhparam -out certs/dh 1024 + # + # dh_file = ${certdir}/dh + + # + # If your system doesn't have /dev/urandom, + # you will need to create this file, and + # periodically change its contents. + # + # For security reasons, FreeRADIUS doesn't + # write to files in its configuration + # directory. + # + # random_file = /dev/urandom + + # + # The default fragment size is 1K. + # However, it's possible to send much more data than + # that over a TCP connection. The upper limit is 64K. + # Setting the fragment size to more than 1K means that + # there are fewer round trips when setting up a TLS + # connection. But only if the certificates are large. + # + fragment_size = 8192 + + # include_length is a flag which is + # by default set to yes If set to + # yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the + # message is included ONLY in the + # First packet of a fragment series. + # + # include_length = yes + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash '. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the line below. + # 5) Restart radiusd + # check_crl = yes + ca_path = ${cadir} + + # OpenSSL does not reload contents of ca_path dir over time. + # That means that if check_crl is enabled and CRLs are loaded + # from ca_path dir, at some point CRLs will expire and + # RADIUSd will stop authenticating NASes. + # If ca_path_reload_interval is non-zero, it will force OpenSSL + # to reload all data from ca_path periodically + # + # Flush ca_path each hour + ca_path_reload_interval = 3600 + + # + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # This check can be done more generally by checking + # the value of the TLS-Client-Cert-Issuer attribute. + # This check can be done via any mechanism you choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Common-Name attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_cn = %{User-Name} + # + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + cipher_list = $ENV{FREERADIUS_SITES_TLS_CIPHER} + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) + # for choosing right cipher suite rather than + # using client-specified list which is OpenSSl default + # behavior. Having it set to yes is a current best practice + # for TLS + cipher_server_preference = no + + # + # Older TLS versions are deprecated. But for RadSec, + # we CAN allow TLS 1.3. + # + tls_min_version = "1.2" + tls_max_version = "1.3" + + # + # Session resumption / fast reauthentication cache. + # + # The cache contains the following information: + # + # session Id - unique identifier, managed by SSL + # User-Name - from the Access-Accept + # Stripped-User-Name - from the Access-Request + # Cached-Session-Policy - from the Access-Accept + # + # The "Cached-Session-Policy" is the name of a + # policy which should be applied to the cached + # session. This policy can be used to assign + # VLANs, IP addresses, etc. It serves as a useful + # way to re-apply the policy from the original + # Access-Accept to the subsequent Access-Accept + # for the cached session. + # + # On session resumption, these attributes are + # copied from the cache, and placed into the + # reply list. + # + # You probably also want "use_tunneled_reply = yes" when using fast session resumption. + # + cache { + # + # Enable it. The default is "no". + # Deleting the entire "cache" subsection + # Also disables caching. + # + # + # As of version 3.0.14, the session cache requires the use + # of the "name" and "persist_dir" configuration items, below. + # + # The internal OpenSSL session cache has been permanently + # disabled. + # + # You can disallow resumption for a + # particular user by adding the following + # attribute to the control item list: + # + # Allow-Session-Resumption = No + # + # If "enable = no" below, you CANNOT + # enable resumption for just one user + # by setting the above attribute to "yes". + # + enable = no + + # + # Lifetime of the cached entries, in hours. + # The sessions will be deleted after this + # time. + # + lifetime = 24 # hours + + # + # Internal "name" of the session cache. + # Used to distinguish which TLS context + # sessions belong to. + # + # The server will generate a random value + # if unset. This will change across server + # restart so you MUST set the "name" if you + # want to persist sessions (see below). + # + # If you use IPv6, change the "ipaddr" below + # to "ipv6addr" + # + #name = "TLS ${..ipaddr} ${..port} ${..proto}" + + # + # Simple directory-based storage of sessions. + # Two files per session will be written, the SSL + # state and the cached VPs. This will persist session + # across server restarts. + # + # The server will need write perms, and the directory + # should be secured from anyone else. You might want + # a script to remove old files from here periodically: + # + # find ${logdir}/tlscache -mtime +2 -exec rm -f {} \; + # + # This feature REQUIRES "name" option be set above. + # + #persist_dir = "${logdir}/tlscache" + } + + # + # Require a client certificate. + # + require_client_cert = yes + + # + # As of version 2.1.10, client certificates can be + # validated via an external command. This allows + # dynamic CRLs or OCSP to be used. + # + # This configuration is commented out in the + # default configuration. Uncomment it, and configure + # the correct paths below to enable it. + # + verify { + # A temporary directory where the client + # certificates are stored. This directory + # MUST be owned by the UID of the server, + # and MUST not be accessible by any other + # users. When the server starts, it will do + # "chmod go-rwx" on the directory, for + # security reasons. The directory MUST + # exist when the server starts. + # + # You should also delete all of the files + # in the directory when the server starts. + # tmpdir = /tmp/radiusd + # tmpdir = /startechnica/freeradius/tmp + + # The command used to verify the client cert. + # We recommend using the OpenSSL command-line + # tool. + # + # The ${..ca_path} text is a reference to + # the ca_path variable defined above. + # + # The %{TLS-Client-Cert-Filename} is the name + # of the temporary file containing the cert + # in PEM format. This file is automatically + # deleted by the server when the command + # returns. + # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" + } + } +} + +clients radsec { + client 127.0.0.1 { + ipaddr = 127.0.0.1 + + # Ensure that this client is TLS *only*. + proto = tls + + # TCP clients can have any shared secret. + # TLS clients MUST have the shared secret set to "radsec". Or, for "proto = tls", you can omit the secret, and it will + # automatically be set to "radsec". + # secret = radsec + secret = $ENV{FREERADIUS_CLIENTS_SECRET} + + # You can also use a "limit" section here. + # See raddb/clients.conf for examples. + # + # Note that BOTH limits are applied. You should therefore set the "listen" limits higher than the ones for each individual + # client. + } +} + +# When a request is proxied to a TLS-enabled home server, the TLS parameters are available via the expansion: +# +# %{proxy_listen: ... } +# +# The contents of the expansion are the same as described above with the %{listen: ... } expansion, and have similar meanings. "client" in this case is the proxy (this system) +# and "server" is the remote system (home server). +# +# Note that the %{proxy_listen: ... } parameters are available only AFTER the connection has been made to the home server. +home_server tls { + ipaddr = 127.0.0.1 + port = $ENV{FREERADIUS_SITES_TLS_PORT} + + # type can be the same types as for the "listen" section/ + # e.g. auth, acct, auth+acct, coa + type = auth + secret = radsec + proto = tcp + status_check = none + + tls { + # + # Similarly to HTTP, the client can use Server Name + # Indication to inform the RadSec server of which + # domain it is requesting. This selection allows + # multiple sites to exist at the same IP address. + # + # For example, and identity provider could host + # multiple sites, but present itself with one public + # IP address. + # + # SNI also permits the use of a load balancer such as + # haproxy. That load balancer can terminate the TLS + # connection, and then use SNI to route the + # underlying RADIUS TCP traffic to a particular host. + # + # Note that "hostname" here is only for SNI, and is NOT + # the hostname or IP address we connect to. For that, + # see "ipaddr", above. + # + # hostname = "example.com" + + private_key_password = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_PASSWORD} + # private_key_file = ${certdir}/client.pem + private_key_file = $ENV{FREERADIUS_SITES_TLS_PRIVKEY_FILE} + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below MUST include not + # only the server certificate, but ALSO all + # of the CA certificates used to sign the + # server certificate. + # certificate_file = ${certdir}/client.pem + certificate_file = $ENV{FREERADIUS_SITES_TLS_CERTIFICATE_FILE} + + # Trusted Root CA list + # + # ALL of the CA's in this list will be trusted to issue client certificates for authentication. + # + # In general, you should use self-signed certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain *one* CA certificate. + # + # This parameter is used only for EAP-TLS, + # when you issue client certificates. If you do + # not use client certificates, and you do not want + # to permit EAP-TLS authentication, then delete + # this configuration item. + ca_file = $ENV{FREERADIUS_SITES_TLS_CA_FILE} + + # + # For TLS-PSK, the key should be specified dynamically, instead of using a hard-coded psk_identity and psk_hexphrase. + # + # The input to the dynamic expansion will be the PSK + # identity supplied by the client, in the + # TLS-PSK-Identity attribute. The output of the + # expansion should be a hex string, of no more than + # 512 characters. The string should not be prefixed + # with "0x". e.g. "abcdef" is OK. "0xabcdef" is not. + # + # psk_query = "%{psksql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}" + + # For DH cipher suites to work, you have to run OpenSSL to create the DH file first: + # + # openssl dhparam -out certs/dh 1024 + # + dh_file = ${certdir}/dh + random_file = /dev/urandom + + # + # The default fragment size is 1K. + # However, TLS can send 64K of data at once. + # It can be useful to set it higher. + # + fragment_size = 8192 + + # include_length is a flag which is + # by default set to yes If set to + # yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the + # message is included ONLY in the + # First packet of a fragment series. + # + # include_length = yes + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash '. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the line below. + # 5) Restart radiusd + # check_crl = yes + ca_path = ${cadir} + + # + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Issuer attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Common-Name attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_cn = %{User-Name} + # + # Set this option to specify the allowed TLS cipher suites. The format is listed in "man 1 ciphers". + cipher_list = $ENV{FREERADIUS_SITES_TLS_CIPHER} + } + +} + +home_server_pool tls { + type = fail-over + home_server = tls +} + +realm tls { + auth_pool = tls +} \ No newline at end of file diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000..3e351c1 --- /dev/null +++ b/index.yaml @@ -0,0 +1,115 @@ +apiVersion: v1 +entries: + freeradius: + - annotations: + category: AccessManagement + apiVersion: v2 + appVersion: 3.2.7 + created: "2025-06-16T16:16:37.456715181+02:00" + dependencies: + - name: st-common + repository: https://startechnica.github.io/apps + version: 0.1.12 + - condition: mariadb.enabled + name: mariadb + repository: oci://registry-1.docker.io/bitnamicharts + version: 20.x.x + description: FreeRADIUS is a modular, high performance free RADIUS suite developed + and distributed under the GNU General Public License, version 2, and is free + for download and use. + digest: da3ba6c773298d8e6ff0df4cb8eb2d9effb81e92d4991a74908a136c70457920 + home: https://github.com/startechnica/apps/tree/main/charts/freeradius + icon: https://freeradius.org/img/wordmark.svg + keywords: + - freeradius + - radius + - mysql + - postgresql + - ldap + kubeVersion: '>=1.24.0-0' + maintainers: + - email: firmansyah@nainggolan.id + name: firmansyahn + url: https://firmansyah.nainggolan.id + name: freeradius + sources: + - https://freeradius.org/ + - https://github.com/FreeRADIUS/freeradius-server + type: application + urls: + - freeradius-1.0.3.tgz + version: 1.0.3 + mariadb: + - annotations: + category: Database + images: | + - name: mariadb + image: docker.io/bitnami/mariadb:11.4.7-debian-12-r1 + - name: mysqld-exporter + image: docker.io/bitnami/mysqld-exporter:0.17.2-debian-12-r11 + - name: os-shell + image: docker.io/bitnami/os-shell:12-debian-12-r46 + licenses: Apache-2.0 + tanzuCategory: service + apiVersion: v2 + appVersion: 11.4.7 + created: "2025-06-16T16:16:37.459591908+02:00" + dependencies: + - name: common + repository: oci://registry-1.docker.io/bitnamicharts + tags: + - bitnami-common + version: 2.x.x + description: MariaDB is an open source, community-developed SQL database server + that is widely in use around the world due to its enterprise features, flexibility, + and collaboration with leading tech firms. + digest: 1e5034974b28d6ab585efdaf9a345ea4e4d3024417b680b83f656a4c0b1e2a84 + home: https://bitnami.com + icon: https://dyltqmyl993wv.cloudfront.net/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + - prometheus + maintainers: + - name: Broadcom, Inc. All Rights Reserved. + url: https://github.com/bitnami/charts + name: mariadb + sources: + - https://github.com/bitnami/charts/tree/main/bitnami/mariadb + urls: + - charts/mariadb-20.5.7.tgz + version: 20.5.7 + st-common: + - annotations: + artifacthub.io/changes: | + - kind: added + description: Add dotenv and envvars names helper + category: Infrastructure + apiVersion: v2 + appVersion: 0.1.12 + created: "2025-06-16T16:16:37.460145288+02:00" + description: A Library Helm Chart for grouping common logic between Startechnica + charts. This chart is not deployable by itself. + digest: 4c1c7304cf4dbb900b6fd847cf1d38e6f8d1db46dbf080b4d52c884b84a36747 + home: https://github.com/startechnica/apps/tree/main/charts/common + icon: https://startechnica.github.io/apps/images/star.png + keywords: + - common + - helper + - template + - function + kubeVersion: '>=1.20.0-0' + maintainers: + - email: firmansyah@nainggolan.id + name: firmansyahn + url: https://firmansyah.nainggolan.id + name: st-common + sources: + - https://startechnica.github.io/apps + type: library + urls: + - charts/st-common-0.1.12.tgz + version: 0.1.12 +generated: "2025-06-16T16:16:37.449242718+02:00" diff --git a/templates/Certificate.yaml b/templates/Certificate.yaml new file mode 100644 index 0000000..9a65f83 --- /dev/null +++ b/templates/Certificate.yaml @@ -0,0 +1,54 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (include "freeradius.createTlsSecret" .) .Values.tls.autoGenerator.certmanager.enabled }} +{{- if not (eq (include "st-common.capabilities.certManager.apiVersion" .) "false") }} +{{- $releaseNamespace := include "st-common.names.namespace" . }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "st-common.names.fullname" . }} +{{- $serviceName := include "st-common.names.fullname" . }} +{{- $altNames := list (printf "%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc" $serviceName $releaseNamespace) (printf "%s.%s" $serviceName $releaseNamespace) $fullname }} +{{/* +{{- $altNames := list (printf "*.%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc" $serviceName $releaseNamespace) (printf "%s.%s" $serviceName $releaseNamespace) $fullname }} +*/}} +apiVersion: {{ include "st-common.capabilities.certManager.apiVersion" . }} +kind: Certificate +metadata: + name: {{ include "st-common.names.fullname" . }}-tls + namespace: {{ include "st-common.names.namespace" . | quote }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} +spec: + secretName: {{ include "freeradius.tlsSecretName" . }} + issuerRef: + group: cert-manager.io + kind: {{ .Values.tls.autoGenerator.certmanager.issuerKind }} + name: {{ .Values.tls.autoGenerator.certmanager.issuerName }} + #name: letsencrypt-prd + privateKey: + algorithm: ECDSA + rotationPolicy: Always + size: 256 + subject: + organizations: + - {{ .Release.Name | quote }} + organizationalUnits: + - {{ include "st-common.names.fullname" . }} + dnsNames: + - {{ .Values.ingress.hostname | quote }} + {{- range .Values.ingress.extraHosts }} + - {{ .name | quote }} + {{- end }} + {{- with $altNames }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} +--- \ No newline at end of file diff --git a/templates/ConfigMap/clients.yaml b/templates/ConfigMap/clients.yaml new file mode 100644 index 0000000..afbe928 --- /dev/null +++ b/templates/ConfigMap/clients.yaml @@ -0,0 +1,23 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if not .Values.clients.existingConfigMapName }} +{{- $client := index .Values "clients" "localhost" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-clients" (include "st-common.names.fullname" .) }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +data: + clients.conf: |- + client +{{- end }} \ No newline at end of file diff --git a/templates/ConfigMap/envvars.yaml b/templates/ConfigMap/envvars.yaml new file mode 100644 index 0000000..2b6b3a1 --- /dev/null +++ b/templates/ConfigMap/envvars.yaml @@ -0,0 +1,75 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "freeradius.names.envvars" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +data: + FREERADIUS_ENABLE_TLS: {{ ternary "true" "false" .Values.tls.enabled | quote }} + + FREERADIUS_CLIENTS_SHORTNAME: "" + FREERADIUS_CLIENTS_IPV4ADDR: "" + FREERADIUS_CLIENTS_IPV6ADDR: "" + FREERADIUS_CLIENTS_SECRET: "" + + {{- if .Values.modsEnabled.sql.enabled }} + FREERADIUS_MODS_SQL_DIALECT: {{ .Values.modsEnabled.sql.dialect }} + FREERADIUS_MODS_SQL_SERVER: {{ include "freeradius.database.host" . | quote }} + FREERADIUS_MODS_SQL_PORT: {{ include "freeradius.database.port" . }} + FREERADIUS_MODS_SQL_LOGIN: {{ include "freeradius.database.user" . | quote }} + FREERADIUS_MODS_SQL_DB: {{ include "freeradius.database.name" . | quote }} + FREERADIUS_MODS_SQL_TABLE_ACCT1: {{ .Values.modsEnabled.sql.table.acct1 }} + FREERADIUS_MODS_SQL_TABLE_ACCT2: {{ .Values.modsEnabled.sql.table.acct2 }} + FREERADIUS_MODS_SQL_TABLE_AUTHCHECK: {{ .Values.modsEnabled.sql.table.authcheck }} + FREERADIUS_MODS_SQL_TABLE_AUTHREPLY: {{ .Values.modsEnabled.sql.table.authreply }} + FREERADIUS_MODS_SQL_TABLE_CLIENT: {{ .Values.modsEnabled.sql.table.client }} + FREERADIUS_MODS_SQL_TABLE_GROUPCHECK: {{ .Values.modsEnabled.sql.table.groupcheck }} + FREERADIUS_MODS_SQL_TABLE_GROUPREPLY: {{ .Values.modsEnabled.sql.table.groupreply }} + FREERADIUS_MODS_SQL_TABLE_POSTAUTH: {{ .Values.modsEnabled.sql.table.postauth }} + FREERADIUS_MODS_SQL_TABLE_USERGROUP: {{ .Values.modsEnabled.sql.table.usergroup }} + FREERADIUS_MODS_SQL_TLS_ENABLE: {{ ternary "yes" "no" .Values.modsEnabled.sql.tls.enabled | quote }} + + FREERADIUS_MODS_SQL_READ_CLIENTS: {{ ternary "yes" "no" .Values.modsEnabled.sql.readClients | quote }} + FREERADIUS_MODS_SQL_GROUP_ATTRIBUTE: {{ .Values.modsEnabled.sql.groupAttribute | quote }} + + FREERADIUS_MODS_SQL_SQLITE_FILENAME: {{ .Values.modsEnabled.sql.sqlite.filename | quote }} + FREERADIUS_MODS_SQL_SQLITE_BUSY_TIMEOUT: {{ .Values.modsEnabled.sql.sqlite.busyTimeout | quote }} + + {{- if .Values.modsEnabled.sql.tls.enabled }} + FREERADIUS_MODS_SQL_TLS_CACERT: {{ include "freeradius.sqlTlsCACert" . | quote }} + FREERADIUS_MODS_SQL_TLS_CIPHER: {{ .Values.modsEnabled.sql.tls.ciphers | quote }} + FREERADIUS_MODS_SQL_TLS_CERTIFICATE: {{ include "freeradius.sqlTlsCert" . | quote }} + FREERADIUS_MODS_SQL_TLS_PRIVATEKEY: {{ include "freeradius.sqlTlsCertKey" . | quote }} + FREERADIUS_MODS_SQL_TLS_PRIVATEKEY_PASSWORD: {{ .Values.modsEnabled.sql.tls.privateKeyPassword | quote }} + {{- end }} + {{- end }} + + FREERADIUS_SITES_NAMESPACE: radius + + FREERADIUS_SITES_DEFAULT_AUTH_PORT: {{ .Values.containerPorts.auth | quote }} + FREERADIUS_SITES_DEFAULT_ACCT_PORT: {{ .Values.containerPorts.acct | quote }} + {{- if .Values.sitesEnabled.coa.enabled }} + FREERADIUS_SITES_COA_PORT: {{ .Values.containerPorts.coa | quote }} + {{- end }} + {{- if .Values.sitesEnabled.status.enabled }} + FREERADIUS_SITES_STATUS_LISTEN: {{ .Values.sitesEnabled.status.listen | quote }} + FREERADIUS_SITES_STATUS_PORT: {{ .Values.containerPorts.status | quote }} + {{- end }} + {{- if .Values.sitesEnabled.tls.enabled }} + FREERADIUS_SITES_TLS_PORT: {{ .Values.containerPorts.radsec | quote }} + FREERADIUS_SITES_TLS_CA_FILE: {{ include "freeradius.tlsCACert" . | quote }} + FREERADIUS_SITES_TLS_CERTIFICATE_FILE: {{ include "freeradius.tlsCert" . | quote }} + FREERADIUS_SITES_TLS_CIPHER: {{ default "DEFAULT" .Values.sitesEnabled.tls.cipher | quote }} + FREERADIUS_SITES_TLS_PRIVKEY_FILE: {{ include "freeradius.tlsCertKey" . | quote }} + {{- end }} diff --git a/templates/ConfigMap/mods-enabled.yaml b/templates/ConfigMap/mods-enabled.yaml new file mode 100644 index 0000000..169bc9f --- /dev/null +++ b/templates/ConfigMap/mods-enabled.yaml @@ -0,0 +1,21 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-mods" (include "st-common.names.fullname" .) }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +data: +{{- if .Values.modsEnabled.sql.enabled }} +{{ (.Files.Glob "files/mods-available/sql").AsConfig | indent 2 }} +{{- end }} diff --git a/templates/ConfigMap/sites-enabled.yaml b/templates/ConfigMap/sites-enabled.yaml new file mode 100644 index 0000000..d751767 --- /dev/null +++ b/templates/ConfigMap/sites-enabled.yaml @@ -0,0 +1,29 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-sites" (include "st-common.names.fullname" .) }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +data: +{{ (.Files.Glob "files/sites-available/default").AsConfig | indent 2 }} +{{ (.Files.Glob "files/sites-available/inner-tunnel").AsConfig | indent 2 }} +{{- if .Values.sitesEnabled.coa.enabled }} +{{ (.Files.Glob "files/sites-available/coa").AsConfig | indent 2 }} +{{- end }} +{{- if .Values.sitesEnabled.status.enabled }} +{{ (.Files.Glob "files/sites-available/status").AsConfig | indent 2 }} +{{- end }} +{{- if .Values.sitesEnabled.tls.enabled }} +{{ (.Files.Glob "files/sites-available/tls").AsConfig | indent 2 }} +{{- end }} \ No newline at end of file diff --git a/templates/Deployment.yaml b/templates/Deployment.yaml new file mode 100644 index 0000000..d173b5c --- /dev/null +++ b/templates/Deployment.yaml @@ -0,0 +1,366 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* vim: set filetype=mustache: */}} + +{{- $globalSecretName := printf "%s" (tpl (include "st-common.secrets.name" (dict "existingSecret" .Values.auth.existingSecret "context" $)) $) }} +apiVersion: {{ include "st-common.capabilities.deployment.apiVersion" . }} +kind: Deployment +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: {{- include "st-common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: freeradius + {{- if .Values.updateStrategy }} + strategy: {{- toYaml .Values.updateStrategy | nindent 4 }} + {{- end }} + template: + metadata: + annotations: + checksum/configmap-env: {{ include (print $.Template.BasePath "/ConfigMap/envvars.yaml") . | sha256sum }} + checksum/configmap-mods: {{ include (print $.Template.BasePath "/ConfigMap/mods-enabled.yaml") . | sha256sum }} + checksum/configmap-sites: {{ include (print $.Template.BasePath "/ConfigMap/sites-enabled.yaml") . | sha256sum }} + checksum/secret-credentials: {{ include (print $.Template.BasePath "/Secret/credentials.yaml") . | sha256sum }} + checksum/secret-sql-tls: {{ include (print $.Template.BasePath "/Secret/sql-tls.yaml") . | sha256sum }} + checksum/secret-tls: {{ include (print $.Template.BasePath "/Secret/tls.yaml") . | sha256sum }} + {{- if .Values.podAnnotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- end }} + labels: {{- include "st-common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: freeradius + {{- if .Values.podLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }} + {{- end }} + spec: + {{- if .Values.affinity }} + affinity: {{- include "st-common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "st-common.affinities.pods" (dict "type" .Values.podAffinityPreset "context" $) | nindent 10 }} + podAntiAffinity: {{- include "st-common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "context" $) | nindent 10 }} + nodeAffinity: {{- include "st-common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- include "freeradius.imagePullSecrets" . | nindent 6 }} + {{- if .Values.hostAliases }} + hostAliases: {{- include "st-common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "st-common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName | quote }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "freeradius.serviceAccountName" . }} + {{- if .Values.tolerations }} + tolerations: {{- include "st-common.tplvalues.render" (dict "value" .Values.tolerations "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "st-common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" .) | nindent 8 }} + {{- end }} + {{- if or (and .Values.volumePermissions.enabled .Values.persistence.enabled) .Values.initContainers }} + initContainers: + {{- if .Values.initContainers }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} + - name: volume-permissions + image: {{ include "freeradius.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + args: + - -ec + - | + printf '%s\n' "[system] Change permission" >&2 + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }} {{ .Values.persistence.mountPath }} + chmod 0711 {{ .Values.persistence.mountPath }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: {{ .Values.persistence.mountPath }} + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- end }} + {{- end }} + containers: + - name: freeradius + image: {{ include "freeradius.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "st-common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.command }} + command: {{- include "st-common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "st-common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.args }} + args: {{- include "st-common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- else }} + args: + - -fxx + - -l + - stdout + {{- end }} + env: + {{- if .Values.modsEnabled.sql.enabled }} + - name: FREERADIUS_MODS_SQL_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.auth.existingSecretPerPassword }} + name: {{ tpl (include "st-common.secrets.name" (dict "existingSecret" .Values.auth.existingSecretPerPassword.databasePassword "context" $)) $ }} + key: {{ include "st-common.secrets.key" (dict "existingSecret" .Values.auth.existingSecretPerPassword "key" "databasePassword") }} + {{- else }} + name: {{ include "freeradius.database.secretName" . }} + key: {{ include "freeradius.database.secretKey" . }} + {{- end }} + {{- end }} + {{- if .Values.sitesEnabled.status.enabled }} + - name: FREERADIUS_SITES_STATUS_SECRET + valueFrom: + secretKeyRef: + {{- if .Values.auth.existingSecretPerPassword }} + name: {{ tpl (include "st-common.secrets.name" (dict "existingSecret" .Values.auth.existingSecretPerPassword.sitesStatusSecret "context" $)) $ }} + key: {{ include "st-common.secrets.key" (dict "existingSecret" .Values.auth.existingSecretPerPassword "key" "sitesStatusSecret") }} + {{- else }} + name: {{ $globalSecretName }} + key: {{ include "st-common.secrets.key" (dict "existingSecret" .Values.auth.existingSecret "key" "sites-status-secret") }} + {{- end }} + {{- end }} + {{- if .Values.sitesEnabled.tls.enabled }} + - name: FREERADIUS_SITES_TLS_PRIVKEY_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.auth.existingSecretPerPassword }} + name: {{ tpl (include "st-common.secrets.name" (dict "existingSecret" .Values.auth.existingSecretPerPassword.sitesTlsPrivKeyPassword "context" $)) $ }} + key: {{ include "st-common.secrets.key" (dict "existingSecret" .Values.auth.existingSecretPerPassword "key" "sitesTlsPrivKeyPassword") }} + {{- else }} + name: {{ $globalSecretName }} + key: {{ include "st-common.secrets.key" (dict "existingSecret" .Values.auth.existingSecret "key" "sites-tls-privkey-password") }} + {{- end }} + {{- end }} + {{- if .Values.extraEnvVars }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + envFrom: + - configMapRef: + name: {{ include "freeradius.names.envvars" . }} + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.extraEnvVarsCM }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.extraEnvVarsSecret }} + {{- end }} + {{- if .Values.lifecycleHooks }} + lifecycle: {{- include "st-common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + ports: + - name: auth + containerPort: {{ .Values.containerPorts.auth }} + protocol: UDP + - name: acct + containerPort: {{ .Values.containerPorts.acct }} + protocol: UDP + {{- if .Values.sitesEnabled.coa.enabled }} + - name: coa + containerPort: {{ .Values.containerPorts.coa }} + protocol: UDP + {{- end }} + {{- if .Values.tls.enabled }} + - name: radsec + containerPort: {{ .Values.containerPorts.radsec }} + protocol: TCP + {{- end }} + {{- if .Values.sitesEnabled.status.enabled }} + - name: status + containerPort: {{ .Values.containerPorts.status }} + protocol: UDP + {{- end }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.customStartupProbe }} + startupProbe: {{- include "st-common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- else if .Values.startupProbe.enabled }} + startupProbe: {{- include "st-common.tplvalues.render" (dict "value" (omit .Values.startupProbe "enabled") "context" $) | nindent 12 }} + exec: + command: + - sh + - -c + - | + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + if [[ ! -f "{{ .Values.persistence.mountPath }}/.user_scripts_initialized" ]]; then + echo "Init scripts still not executed. Skipping check" + exit 1 + fi + {{- end }} + /bin/echo "Message-Authenticator = 0x00" | /usr/bin/radclient 127.0.0.1:${FREERADIUS_SITES_STATUS_PORT} status ${FREERADIUS_SITES_STATUS_SECRET} + {{- end }} + {{- if .Values.customLivenessProbe }} + livenessProbe: {{- include "st-common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- else if .Values.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - sh + - -c + - >- + /bin/echo "Message-Authenticator = 0x00" | /usr/bin/radclient 127.0.0.1:${FREERADIUS_SITES_STATUS_PORT} status ${FREERADIUS_SITES_STATUS_SECRET} + {{- end }} + {{- if .Values.customReadinessProbe }} + readinessProbe: {{- include "st-common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- else if .Values.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }} + exec: + command: + - sh + - -c + - >- + /bin/echo "Message-Authenticator = 0x00" | /usr/bin/radclient 127.0.0.1:${FREERADIUS_SITES_STATUS_PORT} status ${FREERADIUS_SITES_STATUS_SECRET} + {{- end }} + {{- end }} + {{- if .resources }} + resources: {{- include "st-common.tplvalues.render" (dict "value" .resources "context" $) | nindent 12 }} + {{- else if and .resourcesPreset (ne .resourcesPreset "none") }} + resources: {{- include "st-common.resources.preset" (dict "type" .resourcesPreset) | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: {{ default "/startechnica/freeradius" .Values.persistence.mountPath }} + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if or (.Files.Glob "files/radiusd.conf") .Values.configuration .Values.configurationConfigMap }} + - name: freeradius-config + mountPath: /etc/freeradius/radiusd.conf + subPath: radiusd.conf + {{- end }} + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d + {{- end }} + {{- if .Values.modsEnabled.sql.enabled }} + - name: freeradius-mods + mountPath: /etc/freeradius/mods-enabled/sql + subPath: sql + {{- end }} + - name: freeradius-sites + mountPath: /etc/freeradius/sites-enabled/default + subPath: default + - name: freeradius-sites + mountPath: /etc/freeradius/sites-enabled/status + subPath: status + {{- if .Values.sitesEnabled.coa.enabled }} + - name: freeradius-sites + mountPath: /etc/freeradius/sites-enabled/coa + subPath: coa + {{- end }} + {{- if .Values.tls.enabled }} + - name: freeradius-sites + mountPath: /etc/freeradius/sites-enabled/tls + subPath: tls + - name: freeradius-tls + mountPath: /opt/startechnica/freeradius/certs + readOnly: true + {{- end }} + {{- if and .Values.modsEnabled.sql.enabled (eq .Values.modsEnabled.sql.dialect "sqlite") }} + - name: freeradius-sqlite + mountPath: {{ .Values.modsEnabled.sql.sqlite.filename | quote }} + {{- end }} + {{- if .Values.modsEnabled.sql.tls.enabled }} + - name: freeradius-sql-tls + mountPath: /opt/startechnica/freeradius/certs + {{- end }} + - name: shared-certs + mountPath: /opt/startechnica/freeradius/shared-certs + readOnly: true + - name: temp + mountPath: /startechnica/freeradius/tmp + {{- if .Values.extraVolumeMounts }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sidecars }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + - name: freeradius-mods + configMap: + name: {{ printf "%s-mods" (include "st-common.names.fullname" .) }} + - name: freeradius-sites + configMap: + name: {{ printf "%s-sites" (include "st-common.names.fullname" .) }} + - name: temp + emptyDir: {} + - name: shared-certs + emptyDir: {} + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "freeradius.claimName" . }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if and .Values.modsEnabled.sql.enabled (eq .Values.modsEnabled.sql.dialect "sqlite") }} + - name: freeradius-sqlite + emptyDir: {} + {{- end }} + {{- if .Values.tls.enabled }} + - name: freeradius-tls + secret: + secretName: {{ include "freeradius.tlsSecretName" . }} + {{- end }} + {{- if .Values.modsEnabled.sql.tls.enabled }} + - name: freeradius-sql-tls + secret: + secretName: {{ include "freeradius.sqlTlsSecretName" . }} + items: + - key: tls.crt + path: sql-tls.crt + - key: tls.key + path: sql-tls.key + - key: ca.crt + path: sql-ca.crt + {{- end }} + {{- if or (.Files.Glob "files/radiusd.conf") .Values.configuration .Values.configurationConfigMap }} + - name: freeradius-config + configMap: + name: {{ include "freeradius.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ include "freeradius.initdbScriptsCM" . }} + {{- end }} + {{- if .Values.extraVolumes }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/templates/Istio/Gateway.yaml b/templates/Istio/Gateway.yaml new file mode 100644 index 0000000..c1232f8 --- /dev/null +++ b/templates/Istio/Gateway.yaml @@ -0,0 +1,69 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.gateway.enabled (not .Values.gateway.existingGateway) }} +{{- if not (eq (include "st-common.capabilities.istioGateway.apiVersion" .) "false") }} +apiVersion: {{ include "st-common.capabilities.istioGateway.apiVersion" . }} +kind: Gateway +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +spec: + selector: + istio.io/gateway-name: {{ default "ingressgateway" .Values.gateway.name }} + servers: + - port: + name: auth + number: {{ .Values.service.ports.auth }} + protocol: UDP + hosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host.name | quote }} + {{- end }} + - port: + name: acct + number: {{ .Values.service.ports.acct }} + protocol: UDP + hosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host.name | quote }} + {{- end }} + - port: + name: coa + number: {{ .Values.service.ports.coa }} + protocol: UDP + hosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host.name | quote }} + {{- end }} + - port: + name: radsec + number: {{ .Values.service.ports.radsec }} + protocol: TLS + hosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host.name | quote }} + {{- end }} + tls: + mode: PASSTHROUGH + {{- if .Values.sitesEnabled.tls.enabled }} + credentialName: {{ include "freeradius.tlsSecretName" . }} + {{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/Istio/VirtualService.yaml b/templates/Istio/VirtualService.yaml new file mode 100644 index 0000000..bbbf70b --- /dev/null +++ b/templates/Istio/VirtualService.yaml @@ -0,0 +1,47 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.gateway.enabled (not .Values.gateway.existingVirtualService) }} +{{- if not (eq (include "st-common.capabilities.istioVirtualService.apiVersion" .) "false") }} +apiVersion: {{ include "st-common.capabilities.istioVirtualService.apiVersion" . }} +kind: VirtualService +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +spec: + gateways: + - {{ printf "%s/%s" (include "st-common.names.namespace" .) (include "st-common.names.fullname" .) }} + hosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host | quote }} + {{- end }} + tls: + - match: + - port: {{ .Values.service.ports.radsec }} + sniHosts: + - {{ .Values.ingress.hostname }} + {{- range $host := .Values.ingress.extraHosts }} + - {{ $host | quote }} + {{- end }} + route: + - destination: + # host: {{ printf "%s.%s.svc.%s" (include "st-common.names.fullname" .) (include "st-common.names.namespace" .) .Values.clusterDomain }} + host: {{ printf "%s.%s.svc.cluster.local" (include "st-common.names.fullname" .) (include "st-common.names.namespace" .) }} + port: + number: {{ .Values.service.ports.radsec }} +{{- end }} +{{- end }} diff --git a/templates/NetworkPolicy.yaml b/templates/NetworkPolicy.yaml new file mode 100644 index 0000000..35e544a --- /dev/null +++ b/templates/NetworkPolicy.yaml @@ -0,0 +1,57 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.networkPolicy.enabled }} +apiVersion: {{ include "st-common.capabilities.networkPolicy.apiVersion" . }} +kind: NetworkPolicy +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: {{- include "st-common.labels.matchLabels" . | nindent 6 }} + ingress: + - ports: + - port: {{ .Values.containerPorts.auth }} + protocol: UDP + - port: {{ .Values.containerPorts.acct }} + protocol: UDP + {{- if .Values.tls.enabled }} + - port: {{ .Values.containerPorts.radsec }} + protocol: TCP + {{- end }} + {{- if .Values.metrics.enabled }} + - port: {{ .Values.containerPorts.metrics }} + protocol: TCP + {{- end }} + {{- if .Values.sitesEnabled.coa.enabled }} + - port: {{ .Values.containerPorts.coa }} + protocol: UDP + {{- end }} + {{- if .Values.sitesEnabled.status.enabled }} + - port: {{ .Values.containerPorts.status }} + protocol: UDP + {{- end }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ include "st-common.names.fullname" . }}-client: "true" + - podSelector: + matchLabels: {{- include "st-common.labels.matchLabels" . | nindent 14 }} + app.kubernetes.io/component: freeradius + {{- if .Values.networkPolicy.additionalRules }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.networkPolicy.additionalRules "context" $) | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/templates/PersistentVolumeClaim.yaml b/templates/PersistentVolumeClaim.yaml new file mode 100644 index 0000000..0cd0acc --- /dev/null +++ b/templates/PersistentVolumeClaim.yaml @@ -0,0 +1,38 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.persistence.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.persistence.annotations }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.persistence.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + accessModes: + {{- if not (empty .Values.persistence.accessModes) }} + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + {{- else }} + - {{ .Values.persistence.accessMode | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- include "st-common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 2 }} +{{- end -}} \ No newline at end of file diff --git a/templates/PodDisruptionBudget.yaml b/templates/PodDisruptionBudget.yaml new file mode 100644 index 0000000..a4b619e --- /dev/null +++ b/templates/PodDisruptionBudget.yaml @@ -0,0 +1,28 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.podDisruptionBudget.create }} +apiVersion: {{ include "st-common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{ include "st-common.labels.matchLabels" . | nindent 6 }} +{{- end }} \ No newline at end of file diff --git a/templates/PrometheusRule.yaml b/templates/PrometheusRule.yaml new file mode 100644 index 0000000..fafd180 --- /dev/null +++ b/templates/PrometheusRule.yaml @@ -0,0 +1,25 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{ if and .Values.metrics.enabled .Values.metrics.prometheusRules.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.metrics.prometheusRules.additionalLabels "context" $) | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +spec: + groups: + - name: {{ include "st-common.names.fullname" . }} + rules: + {{- toYaml .Values.metrics.prometheusRules.rules | nindent 6 }} +{{ end }} \ No newline at end of file diff --git a/templates/Role.yaml b/templates/Role.yaml new file mode 100644 index 0000000..89bcc15 --- /dev/null +++ b/templates/Role.yaml @@ -0,0 +1,29 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.rbac.create }} +apiVersion: {{ include "st-common.capabilities.rbac.apiVersion" . }} +kind: Role +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +rules: + - apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - get + - list + - watch +{{- end }} \ No newline at end of file diff --git a/templates/RoleBinding.yaml b/templates/RoleBinding.yaml new file mode 100644 index 0000000..1975a59 --- /dev/null +++ b/templates/RoleBinding.yaml @@ -0,0 +1,26 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +apiVersion: {{ include "st-common.capabilities.rbac.apiVersion" . }} +kind: RoleBinding +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +subjects: + - kind: ServiceAccount + name: {{ include "freeradius.serviceAccountName" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "st-common.names.fullname" . }} +{{- end }} \ No newline at end of file diff --git a/templates/Secret/credentials.yaml b/templates/Secret/credentials.yaml new file mode 100644 index 0000000..6618ad5 --- /dev/null +++ b/templates/Secret/credentials.yaml @@ -0,0 +1,38 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (not .Values.auth.existingSecret) (not .Values.auth.existingSecretPerPassword) }} +{{- $secretName := include "st-common.secrets.name" (dict "existingSecret" .Values.auth.existingSecret "context" $) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} +type: Opaque +data: + {{- if and (not .Values.mariadb.enabled) (not .Values.externalDatabase.existingSecret) }} + database-password: {{ include "st-common.secrets.passwords.manage" (dict "secret" $secretName "key" "database-password" "length" 10 "providedValues" (list "externalDatabase.password") "context" $) }} + {{- end }} + {{- if and (.Values.mariadb.enabled) (not (empty .Values.mariadb.auth.username)) }} + mariadb-password: {{ include "st-common.secrets.passwords.manage" (dict "secret" $secretName "key" "mariadb-password" "providedValues" (list "mariadb.auth.password") "context" $) }} + {{- end }} + {{- if (.Values.sitesEnabled.status.enabled) }} + sites-status-secret: {{ include "st-common.secrets.passwords.manage" (dict "secret" $secretName "key" "sites-status-secret" "length" 10 "providedValues" (list "sitesEnabled.status.secret") "context" $) }} + {{- end }} + {{- if (.Values.sitesEnabled.tls.enabled) }} + sites-tls-privkey-password: {{ include "st-common.secrets.passwords.manage" (dict "secret" $secretName "key" "sites-tls-privkey-password" "length" 10 "providedValues" (list "sitesEnabled.tls.privateKeyPassword") "context" $) }} + {{- end }} + {{- if (.Values.modsEnabled.sql.tls.enabled) }} + mods-sql-tls-privkey-password: {{ include "st-common.secrets.passwords.manage" (dict "secret" $secretName "key" "mods-sql-tls-privkey-password" "length" 10 "providedValues" (list "modsEnabled.sql.tls.privateKeyPassword") "context" $) }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/Secret/sql-tls.yaml b/templates/Secret/sql-tls.yaml new file mode 100644 index 0000000..e49a5e2 --- /dev/null +++ b/templates/Secret/sql-tls.yaml @@ -0,0 +1,30 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (include "freeradius.createSqlTlsSecret" .) (not .Values.tls.autoGenerator.certmanager.enabled) }} +{{- $ca := genCA "freeradius-ca" 365 }} +{{- $releaseNamespace := include "st-common.names.namespace" . }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "st-common.names.fullname" . }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) (printf "%s.%s.svc" $fullname $releaseNamespace) (printf "%s.%s" $fullname $releaseNamespace) $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "st-common.names.fullname" . }}-sql-tls + namespace: {{ include "st-common.names.namespace" . | quote }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/templates/Secret/tls.yaml b/templates/Secret/tls.yaml new file mode 100644 index 0000000..3b182d5 --- /dev/null +++ b/templates/Secret/tls.yaml @@ -0,0 +1,30 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (include "freeradius.createTlsSecret" .) (not .Values.tls.autoGenerator.certmanager.enabled) }} +{{- $ca := genCA "freeradius-ca" 365 }} +{{- $releaseNamespace := include "st-common.names.namespace" . }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "st-common.names.fullname" . }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) (printf "%s.%s.svc" $fullname $releaseNamespace) (printf "%s.%s" $fullname $releaseNamespace) $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "st-common.names.fullname" . }}-tls + namespace: {{ include "st-common.names.namespace" . | quote }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} \ No newline at end of file diff --git a/templates/Service.yaml b/templates/Service.yaml new file mode 100644 index 0000000..7571125 --- /dev/null +++ b/templates/Service.yaml @@ -0,0 +1,98 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "st-common.names.fullname" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if or .Values.service.annotations .Values.commonAnnotations .Values.metrics.annotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.service.annotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.service.annotations "context" $) | nindent 4 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.annotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.metrics.annotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ default "ClusterIP" .Values.service.type }} + {{- if eq .Values.service.type "LoadBalancer" }} + allocateLoadBalancerNodePorts: {{ eq .Values.service.allocateLoadBalancerNodePorts "true" }} + {{- end }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if and .Values.service.externalTrafficPolicy (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerClass)) }} + loadBalancerClass: {{ .Values.service.loadBalancerClass }} + {{- end }} + {{- if (and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP))) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- end }} + {{- if .Values.service.sessionAffinity }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- end }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "st-common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: udp-auth + port: {{ .Values.service.ports.auth }} + protocol: UDP + targetPort: {{ .Values.containerPorts.auth }} + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.nodePorts.auth) }} + nodePort: {{ coalesce .Values.service.nodePorts.auth .Values.service.nodePort }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + - name: udp-acct + port: {{ .Values.service.ports.acct }} + protocol: UDP + targetPort: {{ .Values.containerPorts.acct }} + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.nodePorts.acct) }} + nodePort: {{ .Values.service.nodePorts.acct }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- if .Values.sitesEnabled.coa.enabled }} + - name: udp-coa + port: {{ .Values.service.ports.coa }} + protocol: UDP + targetPort: {{ .Values.containerPorts.coa }} + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.nodePorts.coa) }} + nodePort: {{ .Values.service.nodePorts.coa }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: tcp-radsec + port: {{ .Values.service.ports.radsec }} + protocol: TCP + targetPort: {{ .Values.containerPorts.radsec }} + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.nodePorts.radsec) }} + nodePort: {{ .Values.service.nodePorts.radsec }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + selector: {{ include "st-common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: freeradius +--- \ No newline at end of file diff --git a/templates/ServiceAccount.yaml b/templates/ServiceAccount.yaml new file mode 100644 index 0000000..9e621e5 --- /dev/null +++ b/templates/ServiceAccount.yaml @@ -0,0 +1,27 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "freeradius.serviceAccountName" . }} + namespace: {{ include "st-common.names.namespace" . | quote }} + labels: {{- include "st-common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: freeradius + {{- if .Values.commonLabels }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if or .Values.serviceAccount.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.serviceAccount.annotations }} + {{- include "st-common.tplvalues.render" (dict "value" .Values.serviceAccount.annotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/templates/_helpers/_databases.tpl b/templates/_helpers/_databases.tpl new file mode 100644 index 0000000..1bb165c --- /dev/null +++ b/templates/_helpers/_databases.tpl @@ -0,0 +1,95 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). */}} +{{- define "freeradius.mariadb.fullname" -}} + {{- include "st-common.names.dependency.fullname" (dict "chartName" "mariadb" "chartValues" .Values.mariadb "context" $) -}} +{{- end -}} + +{{/* Return the Database hostname */}} +{{- define "freeradius.database.host" -}} +{{- if eq .Values.mariadb.architecture "replication" }} + {{- ternary (include "freeradius.mariadb.fullname" .) .Values.externalDatabase.host .Values.mariadb.enabled -}}-primary +{{- else -}} + {{- ternary (include "freeradius.mariadb.fullname" .) .Values.externalDatabase.host .Values.mariadb.enabled -}} +{{- end -}} +{{- end -}} + +{{/* Return the Database port */}} +{{- define "freeradius.database.port" -}} + {{- ternary "3306" .Values.externalDatabase.port .Values.mariadb.enabled | quote -}} +{{- end -}} + +{{/* Return the Database database name */}} +{{- define "freeradius.database.name" -}} +{{- if .Values.mariadb.enabled }} + {{- if .Values.global.mariadb }} + {{- if .Values.global.mariadb.auth }} + {{- coalesce .Values.global.mariadb.auth.database .Values.mariadb.auth.database -}} + {{- else -}} + {{- .Values.mariadb.auth.database -}} + {{- end -}} + {{- else -}} + {{- .Values.mariadb.auth.database -}} + {{- end -}} +{{- else -}} + {{- .Values.externalDatabase.database -}} +{{- end -}} +{{- end -}} + +{{/* Return the Database user */}} +{{- define "freeradius.database.user" -}} +{{- if .Values.mariadb.enabled }} + {{- if .Values.global.mariadb }} + {{- if .Values.global.mariadb.auth }} + {{- coalesce .Values.global.mariadb.auth.username .Values.mariadb.auth.username -}} + {{- else -}} + {{- .Values.mariadb.auth.username -}} + {{- end -}} + {{- else -}} + {{- .Values.mariadb.auth.username -}} + {{- end -}} +{{- else -}} + {{- .Values.externalDatabase.user -}} +{{- end -}} +{{- end -}} + +{{/* Return the Database encrypted password */}} +{{- define "freeradius.database.secretName" -}} +{{- if .Values.mariadb.enabled }} + {{- if .Values.global.mariadb }} + {{- if .Values.global.mariadb.auth }} + {{- if .Values.global.mariadb.auth.existingSecret }} + {{- tpl .Values.global.mariadb.auth.existingSecret $ -}} + {{- else -}} + {{- default (include "freeradius.mariadb.fullname" .) (tpl .Values.mariadb.auth.existingSecret $) -}} + {{- end -}} + {{- else -}} + {{- default (include "freeradius.mariadb.fullname" .) (tpl .Values.mariadb.auth.existingSecret $) -}} + {{- end -}} + {{- else -}} + {{- default (include "freeradius.mariadb.fullname" .) (tpl .Values.mariadb.auth.existingSecret $) -}} + {{- end -}} +{{- else -}} + {{- default (include "st-common.secrets.name" (dict "existingSecret" .Values.mariadb.auth.existingSecret "context" $)) (tpl .Values.externalDatabase.existingSecret $) -}} +{{- end -}} +{{- end -}} + +{{/* Add environment variables to configure database values */}} +{{- define "freeradius.database.secretKey" -}} +{{- if .Values.mariadb.enabled -}} + {{- print "mariadb-password" -}} +{{- else -}} + {{- if .Values.externalDatabase.existingSecret -}} + {{- if .Values.externalDatabase.existingSecretPasswordKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretPasswordKey -}} + {{- else -}} + {{- print "database-password" -}} + {{- end -}} + {{- else -}} + {{- print "database-password" -}} + {{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/templates/_helpers/_helpers.tpl b/templates/_helpers/_helpers.tpl new file mode 100644 index 0000000..35ff55e --- /dev/null +++ b/templates/_helpers/_helpers.tpl @@ -0,0 +1,140 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* Create the name of the service account to use */}} +{{- define "freeradius.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} + {{- default (include "st-common.names.fullname" .) .Values.serviceAccount.name }} +{{- else }} + {{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* Return the path to the cert file. */}} +{{- define "freeradius.tlsCert" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/tls.crt" -}} +{{- else -}} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.tls.certFilename -}} +{{- end -}} +{{- end -}} + +{{/* Return the path to the cert key file. */}} +{{- define "freeradius.tlsCertKey" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/tls.key" -}} +{{- else -}} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.tls.certKeyFilename -}} +{{- end -}} +{{- end -}} + +{{/* Return the path to the CA cert file. */}} +{{- define "freeradius.tlsCACert" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/ca.crt" -}} +{{- else -}} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.tls.certCAFilename -}} +{{- end -}} +{{- end -}} + +{{/* Create the name of the SSL certificate to use */}} +{{- define "freeradius.tlsSecretName" -}} +{{- if .Values.tls.certificatesSecret }} + {{ .Values.tls.certificatesSecret }} +{{- else }} + {{- default (printf "%s-tls" (include "st-common.names.fullname" .)) }} +{{- end }} +{{- end -}} + +{{/* Return true if a TLS secret object should be created */}} +{{- define "freeradius.createTlsSecret" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated (not .Values.tls.certificatesSecret) }} + {{- true }} +{{- end }} +{{- end -}} + +{{/* Validate values of FreeRADIUS - Auth TLS enabled */}} +{{- define "freeradius.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.tls.autoGenerated) (not .Values.tls.certificatesSecret) }} +freeradius: tls.enabled + In order to enable TLS, you also need to provide + an existing secret containing the Keystore and Truststore or + enable auto-generated certificates. +{{- end }} +{{- end -}} + +{{/* Return the path to the SQL cert file. */}} +{{- define "freeradius.sqlTlsCert" -}} +{{- if and .Values.modsEnabled.sql.tls.enabled }} + {{- if .Values.modsEnabled.sql.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/sql-tls.crt" -}} + {{- else if not (empty .Values.modsEnabled.sql.tls.certFilename) }} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.modsEnabled.sql.tls.certFilename -}} + {{- end }} +{{- else }} + {{- printf "" -}} +{{- end }} +{{- end -}} + +{{/* Return the path to the SQL cert key file. */}} +{{- define "freeradius.sqlTlsCertKey" -}} +{{- if and .Values.modsEnabled.sql.tls.enabled }} + {{- if .Values.modsEnabled.sql.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/sql-tls.key" -}} + {{- else if not (empty .Values.modsEnabled.sql.tls.certKeyFilename) }} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.modsEnabled.sql.tls.certKeyFilename -}} + {{- end }} +{{- else }} + {{- printf "" -}} +{{- end }} +{{- end -}} + +{{/* Return the path to the SQL CA cert file. */}} +{{- define "freeradius.sqlTlsCACert" -}} +{{- if and .Values.modsEnabled.sql.tls.enabled }} + {{- if .Values.modsEnabled.sql.tls.autoGenerated }} + {{- printf "/opt/startechnica/freeradius/certs/sql-ca.crt" -}} + {{- else if not (empty .Values.modsEnabled.sql.tls.certCAFilename)}} + {{- printf "/opt/startechnica/freeradius/certs/%s" .Values.modsEnabled.sql.tls.certCAFilename -}} + {{- end }} +{{- else }} + {{- printf "" -}} +{{- end }} +{{- end -}} + +{{/* Create the name of the secret for SQL SSL certificate to use */}} +{{- define "freeradius.sqlTlsSecretName" -}} +{{- if .Values.modsEnabled.sql.tls.certificatesSecret }} + {{ .Values.modsEnabled.sql.tls.certificatesSecret }} +{{- else }} + {{- default (printf "%s-sql-tls" (include "st-common.names.fullname" .)) }} +{{- end }} +{{- end -}} + +{{/* Return true if a TLS secret object should be created */}} +{{- define "freeradius.createSqlTlsSecret" -}} +{{- if and .Values.modsEnabled.sql.tls.enabled .Values.modsEnabled.sql.tls.autoGenerated (not .Values.modsEnabled.sql.tls.certificatesSecret) }} + {{- true }} +{{- end }} +{{- end -}} + +{{/* Get the configuration ConfigMap name. */}} +{{- define "freeradius.configurationCM" -}} +{{- if .Values.configurationConfigMap -}} + {{- printf "%s" (tpl .Values.configurationConfigMap $) -}} +{{- else -}} + {{- printf "%s-configuration" (include "st-common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* Get the initialization scripts ConfigMap name. */}} +{{- define "freeradius.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} + {{- printf "%s" .Values.initdbScriptsConfigMap -}} +{{- else -}} + {{- printf "%s-init-scripts" (include "st-common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + diff --git a/templates/_helpers/_images.tpl b/templates/_helpers/_images.tpl new file mode 100644 index 0000000..4d9840b --- /dev/null +++ b/templates/_helpers/_images.tpl @@ -0,0 +1,14 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* Return the proper FreeRADIUS image name */}} +{{- define "freeradius.image" -}} + {{ include "st-common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* Return the proper Docker Image Registry Secret Names */}} +{{- define "freeradius.imagePullSecrets" -}} + {{- include "st-common.images.pullSecrets" (dict "images" (list .Values.image .Values.volumePermissions.image .Values.metrics.image) "global" .Values.global) -}} +{{- end -}} \ No newline at end of file diff --git a/templates/_helpers/_names.tpl b/templates/_helpers/_names.tpl new file mode 100644 index 0000000..e4c9bec --- /dev/null +++ b/templates/_helpers/_names.tpl @@ -0,0 +1,10 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* vim: set filetype=mustache: */}} + +{{- define "freeradius.names.envvars" -}} +{{- printf "%s-envvars" (include "st-common.names.fullname" .) -}} +{{- end -}} \ No newline at end of file diff --git a/templates/_helpers/_volumes.tpl b/templates/_helpers/_volumes.tpl new file mode 100644 index 0000000..d636769 --- /dev/null +++ b/templates/_helpers/_volumes.tpl @@ -0,0 +1,18 @@ +{{- /* +Copyright (c) 2025 Firmansyah Nainggolan. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* Return the FreeRADIUS PVC name. */}} +{{- define "freeradius.claimName" -}} +{{- if .Values.persistence.existingClaim }} + {{- printf "%s" (tpl .Values.persistence.existingClaim $) -}} +{{- else }} + {{- printf "%s" (include "st-common.names.fullname" .) -}} +{{- end }} +{{- end -}} + +{{/* Return the proper image name (for the init container volume-permissions image) */}} +{{- define "freeradius.volumePermissions.image" -}} + {{ include "st-common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} \ No newline at end of file diff --git a/values-test.yaml b/values-test.yaml new file mode 100644 index 0000000..059a3c5 --- /dev/null +++ b/values-test.yaml @@ -0,0 +1,44 @@ +persistence: + enabled: true +# storageClass: + +service: + type: LoadBalancer + externalTrafficPolicy: Local + ipFamilyPolicy: PreferDualStack + +modsEnabled: + sql: + enabled: true + dialect: mysql + +externalDatabase: + # host: mariadb-infra-mariadb-galera.mariadb-infra.svc + host: mariadb-primary.mariadb.svc + port: 3306 + user: radius_user + database: radiusdb + password: "aserfdertg" + +sitesEnabled: + coa: + enabled: true + tls: + enabled: true + +tls: + enabled: true + autoGenerated: true + +# updateStrategy: +# type: Recreate + +volumePermissions: + enabled: true + +gateway: + enabled: true + dedicated: false + gatewayApi: false + name: "freeradius" + namespace: "istio-ingress" \ No newline at end of file diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..62c1dc7 --- /dev/null +++ b/values.yaml @@ -0,0 +1,985 @@ +## @section Global parameters + +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + +## @section Common parameters + +## @param kubeVersion Force target Kubernetes version (using Helm capabilities if not set) +## +kubeVersion: "" +## @param nameOverride String to partially override freeradius.fullname +## +nameOverride: "" +## @param namespaceOverride String to partially override freeradius.namespace +## +namespaceOverride: "" +## @param fullnameOverride String to fully override adminer.fullname +## +fullnameOverride: "" +## @param commonLabels Labels to add to all deployed objects +## +commonLabels: {} +## @param commonAnnotations Annotations to add to all deployed objects +## +commonAnnotations: {} +## @param clusterDomain Default Kubernetes cluster domain +## +clusterDomain: cluster.local +## @param extraDeploy Array of extra objects to deploy with the release +## +extraDeploy: [] +## Enable diagnostic mode in the deployment +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the deployment + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the deployment + ## + args: + - infinity + +## @section FreeRADIUS Image parameters + +## FreeRADIUS image +## ref: https://hub.docker.com/r/freeradius/freeradius-server/tags +## @param image.registry FreeRADIUS image registry +## @param image.repository FreeRADIUS image repository +## @param image.tag FreeRADIUS image tag (immutable tags are recommended) +## @param image.pullPolicy FreeRADIUS image pull policy +## @param image.pullSecrets Specify docker-registry secret names as an array +## @param image.debug Specify if debug logs should be enabled +## +image: + registry: docker.io + repository: freeradius/freeradius-server + tag: "3.2.7" + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Set to true if you would like to see extra information on logs + ## It turns BASH and/or NAMI debugging in the image + ## + debug: false + +## @param architecture FreeRADIUS architecture (`standalone` or `replication`) +## +architecture: standalone + +auth: + ## @param auth.createClientUser Create client user on boot + ## + createClientUser: true + ## @param auth.clientUser FreeRADIUS administrator user + ## + clientUser: user + ## @param auth.clientPassword FreeRADIUS administrator password for the new user + ## + clientUserPassword: "" + ## @param auth.existingSecret An already existing secret containing auth info + ## e.g: + ## existingSecret: + ## name: mySecret + ## keyMapping: + ## client-user-password: myPasswordKey + ## + existingSecret: "" + ## @param auth.existingSecretPerPassword Override `existingSecret` and other secret values + ## e.g: + ## existingSecretPerPassword: + ## keyMapping: + ## clientUserPassword: FREERADIUS_ADMIN_PASSWORD + ## databasePassword: password + ## databasePassword: + ## name: freeradius.pocwatt-freeradius-cluster.credentials + ## + existingSecretPerPassword: {} + +## @param configuration FreeRADIUS Configuration. Auto-generated based on other parameters when not specified +## Specify content for keycloak.conf +## NOTE: This will override configuring FreeRADIUS based on environment variables (including those set by the chart) +## The radiusd.conf is auto-generated based on other parameters when this parameter is not specified +## +## Example: +## configuration: |- +## foo: bar +## baz: +## +configuration: "" +## @param configurationConfigMap ConfigMap with the FreeRADIUS configuration files (Note: Overrides `radiusdConfiguration`). The value is evaluated as a template. +## +configurationConfigMap: "" +## @param existingConfigmap Name of existing ConfigMap with FreeRADIUS configuration +## NOTE: When it's set the configuration parameter is ignored +## +existingConfigmap: "" +## @param extraStartupArgs Extra default startup args +## +extraStartupArgs: "" +## initdb scripts +## @param initdbScripts Specify dictionary of scripts to be run at first boot +## Alternatively, you can put your scripts under the files/docker-entrypoint-initdb.d directory +## e.g: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/sh +## echo "Do something." +## +initdbScripts: {} +## @param initdbScriptsConfigMap ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) +## +initdbScriptsConfigMap: "" +## @param primary.command Override default container command on FreeRADIUS container(s) (useful when using custom images) +## +command: [] +## @param primary.args Override default container args on FreeRADIUS container(s) (useful when using custom images) +## +args: [] +## @param primary.lifecycleHooks for the FreeRADIUS container(s) to automate configuration before or after startup +## +lifecycleHooks: {} +## @param primary.hostAliases Add deployment host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] +## @param primary.configuration [string] FreeRADIUS configuration to be injected as ConfigMap +## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file +## + +## @section FreeRADIUS Deployment parameters + +## @param replicaCount Desired number of cluster nodes +## +replicaCount: 1 +## @param updateStrategy.type updateStrategy for FreeRADIUS Master StatefulSet +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate +## @param podLabels Extra labels for FreeRADIUS pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} +## @param podAnnotations Annotations for FreeRADIUS pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} +## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAffinityPreset: "" +## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAntiAffinityPreset: soft +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## +nodeAffinityPreset: + ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param nodeAffinityPreset.key Node label key to match. Ignored if `affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + +## @param affinity Affinity for FreeRADIUS pods assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set +## +affinity: {} +## @param nodeSelector Node labels for FreeRADIUS pods assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} +## @param tolerations Tolerations for FreeRADIUS pods assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +## @param topologySpreadConstraints Topology Spread Constraints for FreeRADIUS pods assignment +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ +## E.g. +## topologySpreadConstraints: +## - maxSkew: 1 +## topologyKey: topology.kubernetes.io/zone +## whenUnsatisfiable: DoNotSchedule +## +topologySpreadConstraints: {} + +## @param priorityClassName Priority class for FreeRADIUS pods assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +priorityClassName: "" +## @param schedulerName Name of the k8s scheduler (other than default) +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" +## @param podManagementPolicy podManagementPolicy to manage scaling operation of FreeRADIUS pods +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies +## +podManagementPolicy: "" + +## @param containerPorts.auth FreeRADIUS Auth container port +## @param containerPorts.acct FreeRADIUS Accounting container port +## @param containerPorts.status FreeRADIUS Status HTTP container port +## +containerPorts: + auth: 1812 + acct: 1813 + coa: 3799 + radsec: 2083 + status: 18121 +## FreeRADIUS Pod security context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param podSecurityContext.enabled Enable security context for FreeRADIUS pods +## @param podSecurityContext.fsGroup Group ID for the mounted volumes' filesystem +## +podSecurityContext: + enabled: false + fsGroup: 101 + runAsUser: 101 +## FreeRADIUS container security context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled FreeRADIUS container securityContext +## @param containerSecurityContext.runAsUser User ID for the FreeRADIUS container +## @param containerSecurityContext.runAsNonRoot Set Controller container's Security Context runAsNonRoot +## +containerSecurityContext: + enabled: true + allowPrivilegeEscalation: false + capabilities: + add: + - SYS_PTRACE + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsUser: 101 + runAsNonRoot: true + +## Resource requests and limits +## ref: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ +## We usually recommend not to specify default resources and to leave this as a conscious +## choice for the user. This also increases chances charts run on environments with little +## resources, such as Minikube. If you do want to specify resources, uncomment the following +## lines, adjust them as necessary, and remove the curly braces after 'resources:'. +## @param resourcesPreset Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if resources is set (resources is recommended for production). +## More information: https://github.com/startechnica/apps/blob/main/charts/common/templates/_resources.tpl#L15 +## +resourcesPreset: "nano" +## @param resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) +## Example: +## resources: +## requests: +## cpu: 2 +## memory: 512Mi +## limits: +## cpu: 3 +## memory: 1024Mi +## +resources: {} + +## Configure extra options for FreeRADIUS containers' liveness, readiness and startup probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) +## @param startupProbe.enabled Enable startupProbe +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 120 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 10 + successThreshold: 1 +## Configure extra options for liveness probe +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe +## +livenessProbe: + enabled: true + initialDelaySeconds: 120 + periodSeconds: 60 + timeoutSeconds: 2 + failureThreshold: 3 + successThreshold: 1 +## @param readinessProbe.enabled Enable readinessProbe +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 +## @param customStartupProbe Override default startup probe for FreeRADIUS containers +## +customStartupProbe: {} +## @param customLivenessProbe Override default liveness probe for FreeRADIUS containers +## +customLivenessProbe: {} +## @param customReadinessProbe Override default readiness probe for FreeRADIUS containers +## +customReadinessProbe: {} +## @param startupWaitOptions Override default builtin startup wait check options for FreeRADIUS containers +## `bitnami/mariadb` Docker image has built-in startup check mechanism, +## which periodically checks if FreeRADIUS service has started up and stops it +## if all checks have failed after X tries. Use these to control these checks. +## ref: https://github.com/bitnami/bitnami-docker-mariadb/pull/240 +## Example (with default options): +## startupWaitOptions: +## retries: 300 +## waitTime: 2 +## +startupWaitOptions: {} +## @param extraFlags FreeRADIUS additional command line flags +## Can be used to specify command line flags, for example: +## E.g. +## extraFlags: "--max-connect-errors=1000 --max_connections=155" +## +extraFlags: "" +## @param extraEnvVars Extra environment variables to be set on FreeRADIUS containers +## E.g. +## extraEnvVars: +## - name: TZ +## value: "Europe/Paris" +## +extraEnvVars: [] +## @param extraEnvVarsCM Name of existing ConfigMap containing extra env vars for FreeRADIUS containers +## +extraEnvVarsCM: "" +## @param extraEnvVarsSecret Name of existing Secret containing extra env vars for FreeRADIUS containers +## +extraEnvVarsSecret: "" + +## @section Persistence Parameters + +## Persistence Parameters +## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ +## +persistence: + ## @param persistence.enabled Enable persistence on FreeRADIUS replicas using a `PersistentVolumeClaim` + ## + enabled: false + ## @param persistence.existingClaim Name of an existing `PersistentVolumeClaim` for FreeRADIUS primary replicas + ## NOTE: When it's set the rest of persistence parameters are ignored + ## + existingClaim: "" + ## @param persistence.subPath Subdirectory of the volume to mount at + ## + subPath: "" + ## @param persistence.mountPath Path to mount the volume at + ## + mountPath: /startechnica/freeradius + ## @param persistence.storageClass FreeRADIUS persistent volume storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + storageClass: "" + ## @param persistence.annotations FreeRADIUS persistent volume claim annotations + ## + annotations: {} + ## @param persistence.accessModes FreeRADIUS persistent volume access Modes + ## + accessModes: + - ReadWriteOnce + ## @param persistence.size FreeRADIUS persistent volume size + ## + size: 8Gi + ## @param persistence.selector Selector to match an existing Persistent Volume + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + +## 'volumePermissions' init container parameters +## Changes the owner and group of the persistent volume mount point to runAsUser:fsGroup values +## based on the podSecurityContext/containerSecurityContext parameters +## +volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` + ## + enabled: false + ## Bitnami Shell image + ## ref: https://hub.docker.com/r/bitnami/bitnami-shell/tags/ + ## @param volumePermissions.image.registry Bitnami Shell image registry + ## @param volumePermissions.image.repository Bitnami Shell image repository + ## @param volumePermissions.image.tag Bitnami Shell image tag (immutable tags are recommended) + ## @param volumePermissions.image.pullPolicy Bitnami Shell image pull policy + ## @param volumePermissions.image.pullSecrets Bitnami Shell image pull secrets + ## + image: + registry: docker.io + repository: bitnami/os-shell + tag: "11" + digest: "" + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init container's resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param volumePermissions.resources.limits The resources limits for the init container + ## @param volumePermissions.resources.requests The requested resources for the init container + ## + resources: + limits: {} + requests: {} + ## Init container Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param volumePermissions.securityContext.runAsUser Set init container's Security Context runAsUser + ## NOTE: when runAsUser is set to special value "auto", init container will try to chown the + ## data folder to auto-determined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic user ids (and 0 is not allowed) + ## + securityContext: + runAsUser: 0 + +## @param extraVolumes Optionally specify extra list of additional volumes to the FreeRADIUS pod(s) +## +extraVolumes: [] +## @param extraVolumeMounts Optionally specify extra list of additional volumeMounts for the FreeRADIUS container(s) +## +extraVolumeMounts: [] +## @param initContainers Add additional init containers for the FreeRADIUS pod(s) +## +initContainers: [] +## @param sidecars Add additional sidecar containers for the FreeRADIUS pod(s) +## +sidecars: [] + +## @section Traffic Exposure Parameters + +## FreeRADIUS service parameters +## +service: + ## @param service.type FreeRADIUS Kubernetes service type + ## + type: ClusterIP + ## @param service.ports.auth FreeRADIUS Kubernetes service port + ## + ports: + auth: 1812 + acct: 1813 + coa: 3799 + radsec: 2083 + status: 18121 + ## @param service.nodePorts.mysql FreeRADIUS Kubernetes service node port + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePorts: + auth: "" + acct: "" + coa: "" + radsec: "" + status: "" + ## @param service.clusterIP FreeRADIUS Kubernetes service clusterIP IP + ## + clusterIP: "" + ## @param service.loadBalancerIP FreeRADIUS loadBalancerIP if service type is `LoadBalancer` + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param service.ipFamilyPolicy FreeRADIUS Kubernetes service ipFamilyPolicy policy + ## + ipFamilyPolicy: SingleStack + ## @param service.externalTrafficPolicy Enable client source IP preservation + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.allocateLoadBalancerNodePorts Allow users to disable node ports for Service Type=LoadBalancer. This is useful for + ## bare metal / on-prem environments that rely on VIP based LB implementations. + ## ref https://kubernetes.io/docs/concepts/services-networking/service/#load-balancer-nodeport-allocation + ## + allocateLoadBalancerNodePorts: "false" + ## @param service.loadBalancerClass Enables to use a load balancer implementation other than the cloud provider default. + ## https://kubernetes.io/docs/concepts/services-networking/service/#load-balancer-class + ## + loadBalancerClass: "" + ## @param service.loadBalancerSourceRanges Address that are allowed when FreeRADIUS service is LoadBalancer + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## E.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param service.extraPorts Extra ports to expose (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param service.annotations Provide any additional annotations which may be required + ## + annotations: {} + ## @param service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP" + ## If "ClientIP", consecutive client requests will be directed to the same Pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + sessionAffinityConfig: {} +## Configure the ingress resource that allows you to access the FreeRADIUS installation +## ref: https://kubernetes.io/docs/user-guide/ingress/ +## +ingress: + ## @param ingress.enabled Enable ingress record generation for FreeRADIUS + ## + enabled: false + ## @param ingress.pathType Ingress path type + ## + pathType: ImplementationSpecific + ## @param ingress.apiVersion Force Ingress API version (automatically detected if not set) + ## + apiVersion: "" + ## @param ingress.hostname Default host for the ingress record + ## + hostname: freeradius.local + ## @param ingress.path Default path for the ingress record + ## NOTE: You may need to set this to '/*' in order to use this with ALB ingress controllers + ## + path: / + ## @param ingress.annotations Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. + ## For a full list of possible ingress annotations, please see + ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md + ## Use this parameter to set the required annotations for cert-manager, see + ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations + ## + ## e.g: + ## annotations: + ## kubernetes.io/ingress.class: nginx + ## cert-manager.io/cluster-issuer: cluster-issuer-name + ## + annotations: {} + ## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter + ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" .Values.ingress.hostname }}` + ## You can: + ## - Use the `ingress.secrets` parameter to create this TLS secret + ## - Rely on cert-manager to create it by setting the corresponding annotations + ## - Rely on Helm to create self-signed certificates by setting `ingress.selfSigned=true` + ## + tls: false + ## DEPRECATED: Use ingress.annotations instead of ingress.certManager + ## certManager: false + ## + + ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm + ## + selfSigned: false + ## @param ingress.extraHosts An array with additional hostname(s) to be covered with the ingress record + ## e.g: + ## extraHosts: + ## - name: freeradius.local + ## path: / + ## + extraHosts: [] + ## @param ingress.extraPaths An array with additional arbitrary paths that may need to be added to the ingress under the main host + ## e.g: + ## extraPaths: + ## - path: /* + ## backend: + ## serviceName: ssl-redirect + ## servicePort: use-annotation + ## + extraPaths: [] + ## @param ingress.extraTls TLS configuration for additional hostname(s) to be covered with this ingress record + ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls + ## e.g: + ## extraTls: + ## - hosts: + ## - freeradius.local + ## secretName: freeradius.local-tls + ## + extraTls: [] + ## @param ingress.secrets Custom TLS certificates as secrets + ## NOTE: 'key' and 'certificate' are expected in PEM format + ## NOTE: 'name' should line up with a 'secretName' set further up + ## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates + ## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created valid for 365 days + ## It is also possible to create and manage the certificates outside of this helm chart + ## Please see README.md for more information + ## e.g: + ## secrets: + ## - name: freeradius.local-tls + ## key: |- + ## -----BEGIN RSA PRIVATE KEY----- + ## ... + ## -----END RSA PRIVATE KEY----- + ## certificate: |- + ## -----BEGIN CERTIFICATE----- + ## ... + ## -----END CERTIFICATE----- + ## + secrets: [] + ## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) + ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster . + ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ + ## + ingressClassName: "" + ## @param ingress.extraRules Additional rules to be covered with this ingress record + ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules + ## e.g: + ## extraRules: + ## - host: example.local + ## http: + ## path: / + ## backend: + ## service: + ## name: example-svc + ## port: + ## name: http + ## + extraRules: [] + +## @param revisionHistoryLimit Maximum number of revisions that will be maintained in the Deployment +## +revisionHistoryLimit: 3 + +## @section RBAC parameter +# + +## Specifies whether a ServiceAccount should be created +## +serviceAccount: + ## @param serviceAccount.create Enable the creation of a ServiceAccount for Adminer pods + ## + create: true + ## @param serviceAccount.name Name of the created ServiceAccount + ## If not set and create is true, a name is generated using the fullname template + ## + name: "" + ## @param serviceAccount.automountServiceAccountToken Auto-mount the service account token in the pod + ## + automountServiceAccountToken: false + ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount + ## + annotations: {} +## Role Based Access +## Ref: https://kubernetes.io/docs/admin/authorization/rbac/ +## +rbac: + ## @param rbac.create Specify whether RBAC resources should be created and used + ## + create: false + ## @param rbac.rules Custom RBAC rules + ## Example: + ## rules: + ## - apiGroups: + ## - "" + ## resources: + ## - pods + ## verbs: + ## - get + ## - list + ## + rules: [] + +## Network Policy configuration +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Enable the default NetworkPolicy policy + ## + enabled: false + ## @param networkPolicy.allowExternal Don't require client label for connections + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the ports Keycloak is listening + ## on. When true, Keycloak will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.additionalRules Additional NetworkPolicy rules + ## Note that all rules are OR-ed. + ## Example: + ## additionalRules: + ## - matchLabels: + ## - role: frontend + ## - matchExpressions: + ## - key: role + ## operator: In + ## values: + ## - frontend + ## + additionalRules: {} + +## Pod disruption budget configuration +## +podDisruptionBudget: + ## @param podDisruptionBudget.create Specifies whether a Pod disruption budget should be created + ## + create: false + ## @param podDisruptionBudget.minAvailable Minimum number / percentage of pods that should remain scheduled + ## + minAvailable: 1 + ## @param podDisruptionBudget.maxUnavailable Maximum number / percentage of pods that may be made unavailable + ## + maxUnavailable: "" + +## MariaDB chart configuration +## ref: https://github.com/bitnami/charts/blob/master/bitnami/mariadb/values.yaml +## @param mariadb.enabled Switch to enable or disable the MariaDB helm chart +## @param mariadb.auth.username Name for a custom user to create +## @param mariadb.auth.password Password for the custom user to create +## @param mariadb.auth.database Name for a custom database to create +## @param mariadb.auth.existingSecret Name of existing secret to use for MariaDB credentials +## @param mariadb.architecture MariaDB architecture (`standalone` or `replication`) +## +mariadb: + enabled: false + auth: + username: freeradius_user + password: "" + database: freeradius_db + existingSecret: "" + architecture: standalone + +## External Database configuration +## All of these values are only used when mariadb.enabled is set to false +## @param externalDatabase.host Database host +## @param externalDatabase.port Database port number +## @param externalDatabase.user Non-root username for FreeRADIUS +## @param externalDatabase.password Password for the non-root username for FreeRADIUS +## @param externalDatabase.database FreeRADIUS database name +## @param externalDatabase.existingSecret Name of an existing secret resource containing the database credentials +## @param externalDatabase.existingSecretPasswordKey Name of an existing secret key containing the database credentials +## +externalDatabase: + host: "" + port: 3306 + user: freeradius_user + database: freeradius_db + password: "" + existingSecret: "" + existingSecretPasswordKey: "" + +modsEnabled: + sql: + enabled: true + dialect: mysql + table: + acct1: radacct + acct2: radacct + authcheck: radcheck + authreply: radreply + client: nas + groupcheck: radgroupcheck + groupreply: radgroupreply + postauth: radpostauth + usergroup: radusergroup + ## @param modsEnabled.sql.groupAttribute The group attribute specific to this instance of rlm_sql + ## + groupAttribute: SQL-Group + ## @param modsEnabled.sql.readClients Set to 'true' to read radius clients from the database ('nas' table) + ## + readClients: true + ## @param modsEnabled.sql.tls.enabled + ## @param modsEnabled.sql.tls.autoGenerated Generate automatically self-signed SQL TLS certificates + ## @param modsEnabled.sql.tls.certificatesSecret + ## @param modsEnabled.sql.tls.certFilename + ## @param modsEnabled.sql.tls.certKeyFilename + ## @param modsEnabled.sql.tls.certCAFilename + ## @param modsEnabled.sql.tls.existingTlsSecret + ## @param modsEnabled.sql.tls.privateKeyPassword + ## + tls: + enabled: false + ciphers: "" + autoGenerated: true + certificatesSecret: "" + certFilename: "" + certKeyFilename: "" + certCAFilename: "" + existingTlsSecret: "" + privateKeyPassword: whatever + + ## @param modsEnabled.sql.sqllite.filename + ## @param modsEnabled.sql.sqllite.busyTimeout + ## + sqlite: + filename: /startechnica/freeradius/freeradius.db + busyTimeout: "200" + +sitesEnabled: + coa: + enabled: false + status: + enabled: true + listen: 127.0.0.1 + secret: adminsecret + tls: + ## @param sitesEnabled.tls.enabled Enable TLS support for radsec traffic + ## + enabled: false + privateKeyPassword: "" + cipher: "DEFAULT" + +clients: + localhost: + ipv4addr: "127.0.0.1" + ipv6addr: "" + proto: udp + secret: password + nasType: other + virtualServer: default + coaServer: coa + limit: + maxConnections: 16 + lifetime: 0 + idleTimeout: 30 + existingConfigMapName: "" + +tls: + ## @param tls.enabled Enable TLS support for FreeRADIUS + ## + enabled: false + ## @param tls.autoGenerated Generate automatically self-signed TLS certificates + ## + autoGenerated: true + autoGenerator: + certmanager: + enabled: false + issuerKind: ClusterIssuer + issuerName: selfsigned-issuer + ## @param tls.certificatesSecret Name of the secret that contains the certificates + ## + certificatesSecret: "" + ## @param tls.certFilename Certificate filename + ## + certFilename: "" + ## @param tls.certKeyFilename Certificate key filename + ## + certKeyFilename: "" + ## @param tls.certCAFilename CA Certificate filename + ## + certCAFilename: "" + + secretName: ~ + existingSecret: "" + +gateway: + enabled: false + dedicated: false + gatewayApi: false + name: "" + namespace: "" + gatewayClassName: istio + ## @param gateway.listeners + ## + listeners: [] + existingGateway: ~ + existingVirtualService: ~ + ## @param gateway.extraRoute Array of extra Kubernetes Gateway API Route to deploy with the release + ## + extraRoute: [] + +## Prometheus exporter configuration +## +metrics: + ## @param metrics.enabled Start a side-car prometheus exporter + ## + enabled: false + ## Bitnami FreeRADIUS Prometheus exporter image + ## ref: + ## @param metrics.image.registry FreeRADIUS Prometheus exporter image registry + ## @param metrics.image.repository FreeRADIUS Prometheus exporter image repository + ## @param metrics.image.tag FreeRADIUS Prometheus exporter image tag (immutable tags are recommended) + ## @param metrics.image.pullPolicy FreeRADIUS Prometheus exporter image pull policy + ## @param metrics.image.pullSecrets FreeRADIUS Prometheus exporter image pull secrets + ## + image: + registry: docker.io + repository: + tag: + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + + ## Prometheus Operator PrometheusRule configuration + ## + prometheusRules: + ## @param metrics.prometheusRules.enabled if `true`, creates a Prometheus Operator PrometheusRule (also requires `metrics.enabled` to be `true`, and makes little sense without ServiceMonitor) + ## + enabled: false + ## @param metrics.prometheusRules.additionalLabels [object] Additional labels to add to the PrometheusRule so it is picked up by the operator + ## If using the [Helm Chart](https://github.com/helm/charts/tree/master/stable/prometheus-operator) this is the name of the Helm release and 'app: prometheus-operator' + ## + additionalLabels: + app: prometheus-operator + release: prometheus + ## @param metrics.prometheusRules.rules PrometheusRule rules to configure + ## e.g: + ## - alert: FreeRADIUS-Down + ## annotations: + ## message: 'FreeRADIUS instance {{ $labels.instance }} is down' + ## summary: FreeRADIUS instance is down + ## expr: absent(up{job="freeradius"} == 1) + ## labels: + ## severity: warning + ## service: freeradius + ## for: 5m + ## + rules: {}