NAS-119343 / 23.10 / add tftpd-hpa (#1195)

* fix a bug in common in regards to sysctls

* add netboot

* switch to tftp container

* perm -> owner

* make small adjustment in permission common container

* update questions

* 2 tests

* no double bump

* update readme

* update  readmes

* test app

* umask

* capability

* update chmod and readmes

* remove test app and update metadata

* untouch

* update readme

* update questions

* update strategy

* Address review

* update image
This commit is contained in:
Stavros Kois
2023-05-30 16:26:55 +03:00
committed by GitHub
parent 0430895bfa
commit 3e75be5fa8
22 changed files with 494 additions and 20 deletions

View File

@@ -20,7 +20,7 @@ jobs:
helm-version:
- v3.9.4
- v3.10.3
- v3.11.3
- v3.12.0
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
@@ -87,7 +87,7 @@ jobs:
# We run tests on Helm version of latest SCALE release, SCALE nightly and manually defined "latest"
helm-version:
- v3.9.4
- v3.11.3
- v3.12.0
# We run tests on k3s version of latest SCALE release, SCALE nightly and manually defined "latest"
k3s-version:
- v1.25.3+k3s1

View File

@@ -19,7 +19,7 @@ jobs:
helm-version:
- v3.9.4
- v3.10.3
- v3.11.1
- v3.12.0
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
@@ -57,7 +57,7 @@ jobs:
helm-version:
- v3.9.4
- v3.10.3
- v3.11.1
- v3.12.0
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
@@ -97,7 +97,7 @@ jobs:
- v1.25.3+k3s1
# We run tests on Helm version of latest SCALE release, SCALE nightly and manually defined "latest"
helm-version:
- v3.11.1
- v3.12.0
values:
- basic-values.yaml
- configmap-values.yaml

View File

@@ -220,6 +220,43 @@ tests:
- name: net.ipv4.ip_unprivileged_port_start
value: "443"
- it: should pass with sysctls net.ipv4.ip_unprivileged_port_start NOT appended with hostnet
set:
workload:
workload-name1:
enabled: true
primary: true
type: Deployment
podSpec:
hostNetwork: true
workload-name2:
enabled: true
type: Deployment
podSpec: {}
service:
service-name:
enabled: true
primary: true
type: ClusterIP
targetSelector: workload-name2
ports:
port-name:
enabled: true
primary: true
port: 443
asserts:
- documentIndex: &deploymentDoc 0
isKind:
of: Deployment
- documentIndex: *deploymentDoc
equal:
path: spec.template.spec.securityContext
value:
fsGroup: 568
fsGroupChangePolicy: OnRootMismatch
supplementalGroups: []
sysctls: []
- it: should pass with fsGroup 0
set:
securityContext:

View File

@@ -2,7 +2,7 @@ apiVersion: v2
name: common
description: A library chart for iX Official Catalog
type: library
version: 1.0.7
version: 1.0.8
appVersion: v1
annotations:
title: Common Library Chart

View File

@@ -10,6 +10,7 @@ GID: GID to change permissions to
{{- $type := .type | default "install" -}}
{{- $containerName := .containerName | default "permissions" -}}
{{- $mode := .mode | default "always" -}}
{{- $chmod := .chmod | default "" -}}
{{- $UID := .UID -}}
{{- $GID := .GID -}}
@@ -47,6 +48,9 @@ GID: GID to change permissions to
capabilities:
add:
- CHOWN
{{- if $chmod }}
- FOWNER
{{- end }}
command: bash
args:
- -c
@@ -57,30 +61,51 @@ GID: GID to change permissions to
continue
fi
echo "Current Permissions on ["$dir"]:"
stat -c "%u %g" "$dir"
echo "Current Ownership and Permissions on ["$dir"]:"
echo "chown: $(stat -c "%u %g" "$dir")"
echo "chmod: $(stat -c "%a" "$dir")"
{{- if eq $mode "check" }} {{/* If mode is check, check parent dir */}}
if [ $(stat -c %u "$dir") -eq {{ $UID }} ] && [ $(stat -c %g "$dir") -eq {{ $GID }} ]; then
echo "Permissions are correct. Skipping..."
fix_perms="false"
echo "Ownership is correct. Skipping..."
fix_owner="false"
else
echo "Permissions are incorrect. Fixing..."
fix_perms="true"
echo "Ownership is incorrect. Fixing..."
fix_owner="true"
fi
{{- if $chmod }} {{/* Only if chmod value is given */}}
if [ $(stat -c %a "$dir") -eq {{ $chmod }} ]; then
echo "Permissions are correct. Skipping..."
fix_perms="false"
else
echo "Permissions are incorrect. Fixing..."
fix_perms="true"
fi
{{- end }}
{{- else if eq $mode "always" }} {{/* If mode is always, always fix perms */}}
fix_owner="true"
fix_perms="true"
{{- end }}
if [ "$fix_perms" = "true" ]; then
{{/* Apply changes */}}
if [ "$fix_owner" = "true" ]; then
echo "Changing ownership to {{ $UID }}:{{ $GID }} on: ["$dir"]"
chown -R {{ $UID }}:{{ $GID }} "$dir"
echo "Finished changing ownership"
echo "Permissions after changing ownership:"
echo "Ownership after changes:"
stat -c "%u %g" "$dir"
fi
{{- if $chmod }} {{/* Only if chmod value is given */}}
if [ "$fix_perms" = "true" ]; then
echo "Changing permissions to {{ $chmod }} on: ["$dir"]"
chmod -R {{ $chmod }} "$dir"
echo "Finished changing permissions"
echo "Permissions after changes:"
stat -c "%a" "$dir"
fi
{{- end }}
done
{{- end -}}

View File

@@ -15,13 +15,13 @@ objectData: The object data to be used to render the Pod.
{{/* Initialize from the "global" option */}}
{{- $secContext := mustDeepCopy $rootCtx.Values.securityContext.pod -}}
{{/* Override with pod's option */}}
{{/* Override with pods option */}}
{{- with $objectData.podSpec.securityContext -}}
{{- $secContext = mustMergeOverwrite $secContext . -}}
{{- end -}}
{{/* TODO: Add supplemental groups
devices (5, 10, 20, 24) (Only when devices is assigned on the pod's containers)
devices (5, 10, 20, 24) (Only when devices is assigned on the pods containers)
TODO: Unit Test the above cases
*/}}
@@ -32,7 +32,7 @@ objectData: The object data to be used to render the Pod.
{{- if mustHas $objectData.shortName ($GPUValues.targetSelector | keys) -}}
{{- $gpuAdded = true -}}
{{- end -}}
{{/* If there isn't a selector, but pod is primary */}}
{{/* If there isnt a selector, but pod is primary */}}
{{- else if $objectData.primary -}}
{{- $gpuAdded = true -}}
{{- end -}}
@@ -44,7 +44,9 @@ objectData: The object data to be used to render the Pod.
{{- $portRange := fromJson (include "ix.v1.common.lib.helpers.securityContext.getPortRange" (dict "rootCtx" $rootCtx "objectData" $objectData)) -}}
{{- if and $portRange.low (le (int $portRange.low) 1024) -}} {{/* If a container wants to bind a port <= 1024 change the unprivileged_port_start */}}
{{- $_ := set $secContext "sysctls" (mustAppend $secContext.sysctls (dict "name" "net.ipv4.ip_unprivileged_port_start" "value" (printf "%v" $portRange.low))) -}}
{{- if ne (include "ix.v1.common.lib.pod.hostNetwork" (dict "rootCtx" $rootCtx "objectData" $objectData)) "true" -}}
{{- $_ := set $secContext "sysctls" (mustAppend $secContext.sysctls (dict "name" "net.ipv4.ip_unprivileged_port_start" "value" (printf "%v" $portRange.low))) -}}
{{- end -}}
{{- end -}}
{{- if or (kindIs "invalid" $secContext.fsGroup) (eq (toString $secContext.fsGroup) "") -}}

View File

@@ -0,0 +1,6 @@
dependencies:
- name: common
repository: file://../../../common
version: 1.0.8
digest: sha256:254efaa1285f634b7a80b7baadeadbd20a680f7fee49d1d9d3c4618aa0d657ad
generated: "2023-05-15T16:04:33.567206466+03:00"

View File

@@ -0,0 +1,26 @@
name: tftpd-hpa
description: A lightweight tftp-server
annotations:
title: TFTP Server
type: application
version: 1.0.0
apiVersion: v2
appVersion: 1.0.0
kubeVersion: '>=1.16.0-0'
maintainers:
- name: truenas
url: https://www.truenas.com/
email: dev@ixsystems.com
dependencies:
- name: common
repository: file://../../../common
version: 1.0.8
home: https://github.com/truenas/containers/tree/main/apps/tftpd-hpa
icon: https://avatars.githubusercontent.com/u/53482242
sources:
- https://github.com/truenas/containers/tree/main/apps/tftpd-hpa
- https://github.com/truenas/charts/tree/master/community/tftpd-hpa
- https://hub.docker.com/r/ixsystems/tftpd-hpa
keywords:
- tftp
- netboot

View File

@@ -0,0 +1,14 @@
# TFTP
[TFTP](https://manpages.debian.org/testing/tftpd-hpa/tftpd.8.en.html) is a server for the Trivial File Transfer Protocol.
The app runs as `root` user and drops privileges to `tftp` (9069) user for the TFTP service.
> On every application start, a container will be launched with **root** privileges.
> This will check the parent directory permissions and ownership.
> If there is a mismatch it will apply the correct permissions to the TFTP directories.
> When "Allow Create" is checked, the above container will also check and `chmod` if needed
> to `757` the TFTP directories and to `555` when not checked.
> Afterward, the `TFTP` container will run as a **root** user, dropping privileges
> to `tftp` (9069) user for the TFTP service.
> Note: You need to have configured DHCP server for network boot to work.

View File

@@ -0,0 +1,14 @@
# TFTP
[TFTP](https://manpages.debian.org/testing/tftpd-hpa/tftpd.8.en.html) is a server for the Trivial File Transfer Protocol.
The app runs as `root` user and drops privileges to `tftp` (9069) user for the TFTP service.
> On every application start, a container will be launched with **root** privileges.
> This will check the parent directory permissions and ownership.
> If there is a mismatch it will apply the correct permissions to the TFTP directories.
> When "Allow Create" is checked, the above container will also check and `chmod` if needed
> to `757` the TFTP directories and to `555` when not checked.
> Afterward, the `TFTP` container will run as a **root** user, dropping privileges
> to `tftp` (9069) user for the TFTP service.
> Note: You need to have configured DHCP server for network boot to work.

View File

@@ -0,0 +1,6 @@
tftpStorage:
tftpboot:
type: hostPath
hostPath: /mnt/{{ .Release.Name }}/tftpboot
tftpConfig:
allowCreate: true

View File

@@ -0,0 +1,6 @@
tftpStorage:
tftpboot:
type: hostPath
hostPath: /mnt/{{ .Release.Name }}/tftpboot
tftpConfig:
allowCreate: false

View File

@@ -0,0 +1,6 @@
icon_url: https://avatars.githubusercontent.com/u/53482242
screenshots: []
categories:
- networking
tags:
- tftp

View File

@@ -0,0 +1,16 @@
runAsContext:
- userName: root
groupName: root
gid: 0
uid: 0
description: TFTP requires root privileges to start it's processes.
capabilities:
- name: NET_BIND_SERVICE
description: TFTP requires this ability to bind to port 69 for TFTP.
- name: SETUID
description: TFTP requires this ability to switch user for sub-processes.
- name: SETGID
description: TFTP requires this ability to switch group for sub-processes.
- name: SYS_CHROOT
description: TFTP requires this ability to use chroot for it's sub-processes.
hostMounts: []

View File

@@ -0,0 +1,155 @@
groups:
- name: TFTP Configuration
description: Configure TFTP
- name: Network Configuration
description: Configure Network for TFTP
- name: Storage Configuration
description: Configure Storage for TFTP
- name: Resources Configuration
description: Configure Resources for TFTP
questions:
- variable: TZ
group: TFTP Configuration
label: Timezone
schema:
type: string
default: Etc/UTC
required: true
$ref:
- definitions/timezone
- variable: tftpConfig
label: ""
group: TFTP Configuration
schema:
type: dict
attrs:
- variable: allowCreate
label: Allow Create
description: |
Allow new files to be created. This is disabled by default.</br>
Enabling this will change the permissions of the tftpboot directory to 757.
Disabling this will change the permissions of the tftpboot directory to 555.
schema:
type: boolean
default: false
- variable: additionalEnvs
label: Additional Environment Variables
description: Configure additional environment variables for TFTP.
schema:
type: list
default: []
items:
- variable: env
label: Environment Variable
schema:
type: dict
attrs:
- variable: name
label: Name
schema:
type: string
required: true
- variable: value
label: Value
schema:
type: string
required: true
- variable: tftpNetwork
label: ""
group: Network Configuration
schema:
type: dict
attrs:
- variable: hostNetwork
label: Host Network
description: |
Bind to the host network. It's recommended to keep this disabled.</br>
schema:
type: boolean
default: true
- variable: tftpPort
label: TFTP Port
description: |
The port for TFTP.
When hostNetwork is enabled, the port tftp port will be 69.
schema:
type: int
default: 30000
show_if: [["hostNetwork", "=", false]]
min: 9000
max: 65535
required: true
- variable: tftpStorage
label: ""
group: Storage Configuration
schema:
type: dict
attrs:
- variable: tftpboot
label: TFTP Boot Storage
description: The path to store TFTP Boot files.
schema:
type: dict
attrs:
- variable: type
label: Type
description: |
ixVolume: Is dataset created automatically by the system.</br>
Host Path: Is a path that already exists on the system.
schema:
type: string
required: true
default: "ixVolume"
enum:
- value: "hostPath"
description: Host Path (Path that already exists on the system)
- value: "ixVolume"
description: ixVolume (Dataset created automatically by the system)
- variable: datasetName
label: Dataset Name
schema:
type: string
show_if: [["type", "=", "ixVolume"]]
required: true
hidden: true
immutable: true
default: "tftpboot"
$ref:
- "normalize/ixVolume"
- variable: hostPath
label: Host Path
schema:
type: hostpath
show_if: [["type", "=", "hostPath"]]
immutable: true
required: true
- variable: resources
group: Resources Configuration
label: ""
schema:
type: dict
attrs:
- variable: limits
label: Limits
schema:
type: dict
attrs:
- variable: cpu
label: CPU
description: CPU limit for TFTP.
schema:
type: string
default: "4000m"
required: true
- variable: memory
label: Memory
description: Memory limit for TFTP.
schema:
type: string
default: "8Gi"
required: true

View File

@@ -0,0 +1 @@
{{ include "ix.v1.common.lib.chart.notes" $ }}

View File

@@ -0,0 +1,102 @@
{{- define "tftp.workload" -}}
workload:
tftp:
enabled: true
primary: true
type: Deployment
podSpec:
hostNetwork: {{ .Values.tftpNetwork.hostNetwork }}
containers:
tftp:
enabled: true
primary: true
imageSelector: image
securityContext:
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
readOnlyRootFilesystem: false
capabilities:
add:
- NET_BIND_SERVICE
- SETGID
- SETUID
- SYS_CHROOT
env:
MAPFILE: ""
SECURE: "1"
CREATE: {{ ternary "1" "0" .Values.tftpConfig.allowCreate | quote }}
fixedEnv:
UMASK: {{ ternary "020" "" .Values.tftpConfig.allowCreate | quote }}
{{ with .Values.tftpConfig.additionalEnvs }}
envList:
{{ range $env := . }}
- name: {{ $env.name }}
value: {{ $env.value }}
{{ end }}
{{ end }}
probes:
liveness:
enabled: true
type: exec
command:
- /bin/sh
- -c
- |
getent services tftp
readiness:
enabled: true
type: exec
command:
- /bin/sh
- -c
- |
getent services tftp
startup:
enabled: true
type: exec
command:
- /bin/sh
- -c
- |
getent services tftp
initContainers:
{{- include "ix.v1.common.app.permissions" (dict "containerName" "01-permissions"
"UID" 9069
"GID" 9069
"mode" "check"
"chmod" (ternary "757" "555" .Values.tftpConfig.allowCreate)
"type" "init") | nindent 8 }}
{{/* Service */}}
service:
tftp:
enabled: true
primary: true
type: NodePort
targetSelector: tftp
ports:
tftp:
enabled: true
primary: true
port: {{ .Values.tftpNetwork.tftpPort }}
nodePort: {{ .Values.tftpNetwork.tftpPort }}
targetPort: 69
protocol: udp
targetSelector: tftp
{{/* Persistence */}}
persistence:
tftpboot:
enabled: true
type: {{ .Values.tftpStorage.tftpboot.type }}
datasetName: {{ .Values.tftpStorage.tftpboot.datasetName | default "" }}
hostPath: {{ .Values.tftpStorage.tftpboot.hostPath | default "" }}
targetSelector:
tftp:
tftp:
mountPath: /tftpboot
01-permissions:
mountPath: /mnt/directories/tftpboot
{{- end -}}

View File

@@ -0,0 +1,6 @@
{{- include "ix.v1.common.loader.init" . -}}
{{/* Merge the templates with Values */}}
{{- $_ := mustMergeOverwrite .Values (include "tftp.workload" $ | fromYaml) -}}
{{- include "ix.v1.common.loader.apply" . -}}

View File

@@ -0,0 +1 @@
{"filename": "values.yaml", "keys": ["image"]}

View File

@@ -0,0 +1,30 @@
#!/usr/bin/python3
import json
import re
import sys
from catalog_update.upgrade_strategy import semantic_versioning
RE_STABLE_VERSION = re.compile(r'[1-9]+\.[0-9]+\.[0-9]+')
def newer_mapping(image_tags):
key = list(image_tags.keys())[0]
tags = {t: t for t in image_tags[key] if RE_STABLE_VERSION.fullmatch(t)}
version = semantic_versioning(list(tags))
if not version:
return {}
return {
'tags': {key: tags[version]},
'app_version': version,
}
if __name__ == '__main__':
try:
versions_json = json.loads(sys.stdin.read())
except ValueError:
raise ValueError('Invalid json specified')
print(json.dumps(newer_mapping(versions_json)))

View File

@@ -0,0 +1,21 @@
image:
repository: ixsystems/tftpd-hpa
pullPolicy: IfNotPresent
tag: '1.0.0'
resources:
limits:
cpu: 4000m
memory: 8Gi
tftpConfig:
allowCreate: true
additionalEnvs: []
tftpNetwork:
hostNetwork: true
# Only used if hostNetwork is false
tftpPort: 30000
tftpStorage:
tftpboot:
type: ixVolume
datasetName: tftpboot