diff --git a/applications/templates/template-webdav.yml b/applications/templates/template-webdav.yml new file mode 100644 index 000000000..4b37e025f --- /dev/null +++ b/applications/templates/template-webdav.yml @@ -0,0 +1,207 @@ +--- +kind: Template +apiVersion: template.openshift.io/v1 +metadata: + name: webdav + annotations: + openshift.io/display-name: WebDAV Server for existing storage + description: |- + Deploy a WebDAV server on your persistent volume. This provides a container that offers the WebDAV protocol to upload and download files to your DSRI persistent volume. + + The WebDAV server is based on nginx with the DAV module and uses HTTP Basic Authentication. + + 🔑 You must provide a username and password for authentication + + 📂 The server exposes all files from the mounted persistent volume at /data + + 🔗 You can access the WebDAV server using: + - Any WebDAV client (Cyberduck, WinSCP, etc.) + - Mount as network drive on Windows/Mac/Linux + - Command line tools like curl or cadaver + + Example mount on Linux/Mac: + mount -t davfs https://your-route-url /mount/point + + Example with curl: + curl -u username:password -T file.txt https://your-route-url/file.txt + + Visit https://github.com/MaastrichtU-IDS/dsri-documentation for more details + iconClass: icon-load-balancer + tags: webdav,storage,file-server,dav + openshift.io/provider-display-name: University Library, UM + openshift.io/documentation-url: https://maastrichtu-ids.github.io/dsri-documentation/docs/catalog-utilities + openshift.io/support-url: https://maastrichtu-ids.github.io/dsri-documentation/help +labels: + template: webdav +message: |- + The WebDAV server has been deployed successfully! + + Access your WebDAV server at the route URL provided. + Use your WebDAV client with the credentials you specified. + +parameters: +- name: APPLICATION_NAME + displayName: Application name + description: Must be without spaces (use -), and unique in the project. + value: webdav + required: true +- name: WEBDAV_USERNAME + displayName: WebDAV username + description: Username for WebDAV authentication + value: webdav + required: true +- name: WEBDAV_PASSWORD + displayName: WebDAV password + description: Password for WebDAV authentication (use a strong password) + required: true +- name: APPLICATION_IMAGE + displayName: Application image + description: WebDAV server image based on nginx with DAV module. + value: ghcr.io/maastrichtu-library/webdav:latest + required: true +- name: STORAGE_NAME + displayName: Storage name + description: Name of the Persistent Volume Claim that will be exposed by the WebDAV server. + value: storage-name + required: true +- name: STORAGE_FOLDER + displayName: Storage folder + description: Path to the folder used to store your data in the Persistent Volume Claim (leave empty to use the root folder of the storage) + required: false + +objects: +- apiVersion: v1 + kind: Secret + metadata: + name: "${APPLICATION_NAME}" + labels: + app: ${APPLICATION_NAME} + template: webdav + stringData: + webdav-username: "${WEBDAV_USERNAME}" + webdav-password: "${WEBDAV_PASSWORD}" + +- kind: ImageStream + apiVersion: image.openshift.io/v1 + metadata: + name: ${APPLICATION_NAME} + labels: + app: ${APPLICATION_NAME} + template: webdav + spec: + tags: + - name: latest + from: + kind: DockerImage + name: ${APPLICATION_IMAGE} + importPolicy: + scheduled: false + lookupPolicy: + local: true + +- kind: Deployment + apiVersion: apps/v1 + metadata: + name: "${APPLICATION_NAME}" + labels: + app: "${APPLICATION_NAME}" + spec: + strategy: + type: Recreate + replicas: 1 + selector: + matchLabels: + app: "${APPLICATION_NAME}" + deployment: "${APPLICATION_NAME}" + template: + metadata: + annotations: + io.kubernetes.cri-o.TrySkipVolumeSELinuxLabel: 'true' + labels: + app: "${APPLICATION_NAME}" + deployment: "${APPLICATION_NAME}" + spec: + runtimeClassName: selinux + serviceAccountName: anyuid + volumes: + - name: data + persistentVolumeClaim: + claimName: "${STORAGE_NAME}" + containers: + - name: webdav-container + image: ${APPLICATION_IMAGE} + imagePullPolicy: Always + ports: + - containerPort: 80 + protocol: TCP + resources: + limits: + cpu: '2' + memory: 4Gi + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - name: data + mountPath: "/data" + subPath: "${STORAGE_FOLDER}" + env: + - name: WEBDAV_USERNAME + valueFrom: + secretKeyRef: + key: webdav-username + name: "${APPLICATION_NAME}" + - name: WEBDAV_PASSWORD + valueFrom: + secretKeyRef: + key: webdav-password + name: "${APPLICATION_NAME}" + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 10 + periodSeconds: 30 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + automountServiceAccountToken: false + +- kind: Service + apiVersion: v1 + metadata: + name: "${APPLICATION_NAME}" + labels: + app: "${APPLICATION_NAME}" + spec: + ports: + - name: 80-tcp + protocol: TCP + port: 80 + targetPort: 80 + selector: + app: "${APPLICATION_NAME}" + deployment: "${APPLICATION_NAME}" + +- kind: Route + apiVersion: route.openshift.io/v1 + metadata: + name: "${APPLICATION_NAME}" + labels: + app: "${APPLICATION_NAME}" + spec: + host: '' + to: + kind: Service + name: "${APPLICATION_NAME}" + weight: 100 + port: + targetPort: 80-tcp + tls: + termination: edge + insecureEdgeTerminationPolicy: Redirect diff --git a/applications/webdav/Dockerfile b/applications/webdav/Dockerfile new file mode 100644 index 000000000..0aa3247b5 --- /dev/null +++ b/applications/webdav/Dockerfile @@ -0,0 +1,29 @@ +FROM alpine:latest + +# Install nginx, nginx-dav-ext-module, and apache2-utils +RUN apk add --no-cache \ + nginx \ + nginx-mod-http-dav-ext \ + apache2-utils \ + && rm -rf /var/cache/apk/* + +# Create data directory +RUN mkdir -p /data && \ + chown -R nginx:nginx /data && \ + chmod -R 755 /data + +# Create directory for auth files +RUN mkdir -p /etc/nginx/auth && \ + chmod 755 /etc/nginx/auth + +# Copy nginx configuration +COPY nginx.conf /etc/nginx/nginx.conf + +# Copy entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 80 + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/applications/webdav/README.md b/applications/webdav/README.md new file mode 100644 index 000000000..b5bf6bc14 --- /dev/null +++ b/applications/webdav/README.md @@ -0,0 +1,258 @@ +# WebDAV Server + +A secure nginx-based WebDAV server for the DSRI platform. This provides WebDAV protocol access to files stored in your persistent volume. + +## Features + +- Based on nginx Alpine with the official DAV extension module +- HTTP Basic Authentication for security +- Support for all standard WebDAV methods (GET, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND) +- Health check endpoint for monitoring +- CORS support for web clients +- Configurable via environment variables + +## Building the Docker Image + +### Building Locally + +Build the image locally for testing: + +```bash +docker build -t webdav:latest . +``` + +### Pushing to GitHub Container Registry + +To push the image to GitHub Container Registry (ghcr.io), follow these steps: + +1. **Create a GitHub Personal Access Token** + - Go to GitHub Settings → Developer Settings → Personal Access Tokens → Tokens (classic) + - Click "Generate new token (classic)" + - Select at least the `write:packages` scope + - Copy the generated token + +2. **Login to GitHub Container Registry** + ```bash + docker login ghcr.io + # Username: your-github-username + # Password: your-personal-access-token + ``` + +3. **Build the image for linux/amd64 platform** + ```bash + docker build --platform linux/amd64 -t ghcr.io/maastrichtu-ids/webdav:latest . + ``` + + Note: Use `--platform linux/amd64` to ensure compatibility with OpenShift/Kubernetes clusters running on AMD64 architecture. + +4. **Push the image to the registry** + ```bash + docker push ghcr.io/maastrichtu-ids/webdav:latest + ``` + +### Multi-platform builds (optional) + +For building images that work on multiple architectures: + +```bash +docker buildx create --use +docker buildx build --platform linux/amd64,linux/arm64 \ + -t ghcr.io/maastrichtu-ids/webdav:latest \ + --push . +``` + +## Building on OpenShift + +You can build the image directly on OpenShift: + +```bash +# Create a new build +oc new-build --name webdav --binary + +# Start the build from the current directory +oc start-build webdav --from-dir=. --follow --wait + +# Tag the image +oc tag webdav:latest webdav:v1.0 +``` + +## Deploying to DSRI + +### Using the OpenShift Web Console + +1. Go to the DSRI OpenShift web console +2. Navigate to your project +3. Click on "Add" → "From Catalog" +4. Search for "WebDAV" +5. Fill in the parameters: + - **Application name**: Choose a unique name (e.g., `webdav`) + - **WebDAV username**: Your desired username + - **WebDAV password**: A strong password + - **Storage name**: The name of your existing Persistent Volume Claim + - **Storage folder**: (Optional) Subfolder within the PVC to expose + +### Using the Command Line + +First, apply the template to your project: + +```bash +oc apply -f ../templates/template-webdav.yml +``` + +Then create an app from the template: + +```bash +oc new-app webdav \ + -p APPLICATION_NAME=my-webdav \ + -p WEBDAV_USERNAME=myuser \ + -p WEBDAV_PASSWORD=mypassword \ + -p STORAGE_NAME=my-storage-pvc +``` + +## Using the WebDAV Server + +### Get the Route URL + +```bash +oc get route webdav -o jsonpath='{.spec.host}' +``` + +### Access via Command Line + +Upload a file with curl: + +```bash +curl -u username:password -T myfile.txt https://your-webdav-url/myfile.txt +``` + +Download a file: + +```bash +curl -u username:password https://your-webdav-url/myfile.txt -o myfile.txt +``` + +List directory contents: + +```bash +curl -u username:password -X PROPFIND https://your-webdav-url/ +``` + +### Mount as Network Drive + +#### Linux + +Install davfs2: + +```bash +sudo apt-get install davfs2 # Debian/Ubuntu +sudo yum install davfs2 # RHEL/CentOS +``` + +Mount the WebDAV share: + +```bash +sudo mount -t davfs https://your-webdav-url /mnt/webdav +``` + +Or add to `/etc/fstab` for persistent mounting: + +``` +https://your-webdav-url /mnt/webdav davfs user,noauto 0 0 +``` + +#### macOS + +Use Finder: +1. Open Finder +2. Press `Cmd + K` (or Go → Connect to Server) +3. Enter: `https://your-webdav-url` +4. Click Connect and enter your credentials + +Or via command line: + +```bash +mount -t webdav https://your-webdav-url /Volumes/webdav +``` + +#### Windows + +1. Right-click on "This PC" or "My Computer" +2. Select "Map network drive" +3. Choose a drive letter +4. Enter: `https://your-webdav-url` +5. Check "Connect using different credentials" +6. Enter your username and password + +### WebDAV Clients + +Popular WebDAV clients: + +- **Cyberduck** (Windows, macOS) - https://cyberduck.io/ +- **WinSCP** (Windows) - https://winscp.net/ +- **Cadaver** (Linux command line) - `sudo apt-get install cadaver` +- **davfs2** (Linux mount) - `sudo apt-get install davfs2` + +## Configuration + +The WebDAV server is configured via environment variables: + +- `WEBDAV_USERNAME`: Username for authentication (default: `webdav`) +- `WEBDAV_PASSWORD`: Password for authentication (default: `changeme`) + +These are automatically set from the OpenShift Secret when deploying via the template. + +## Security Considerations + +1. **Always use HTTPS**: The OpenShift route is configured with TLS termination +2. **Use strong passwords**: Avoid default passwords in production +3. **Limit access**: Use OpenShift RBAC to control who can deploy WebDAV servers +4. **Monitor access**: Check nginx access logs for suspicious activity + +## Troubleshooting + +### Check logs + +```bash +oc logs -f deployment/webdav +``` + +### Check if the pod is running + +```bash +oc get pods -l app=webdav +``` + +### Test the health endpoint + +```bash +curl https://your-webdav-url/health +``` + +### Common issues + +1. **Authentication fails**: Double-check username and password in the secret +2. **Cannot write files**: Ensure the PVC is mounted correctly and has write permissions +3. **Connection timeout**: Check if the route exists and is accessible + +## Deleting the Application + +To remove the WebDAV server: + +```bash +oc delete all,secret,configmaps,serviceaccount,rolebinding -l app=webdav +``` + +Replace `webdav` with your APPLICATION_NAME if different. + +## Technical Details + +- **Base Image**: nginx:alpine +- **WebDAV Module**: nginx-mod-http-dav-ext +- **Authentication**: HTTP Basic Auth via htpasswd +- **Port**: 80 (HTTP inside container, HTTPS via OpenShift route) +- **Data Directory**: `/data` (mounted from PVC) +- **Health Check**: `/health` endpoint + +## License + +This is part of the DSRI documentation and templates maintained by the University Library at Maastricht University. diff --git a/applications/webdav/entrypoint.sh b/applications/webdav/entrypoint.sh new file mode 100644 index 000000000..7bdcc1004 --- /dev/null +++ b/applications/webdav/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/sh +set -e + +echo "Starting WebDAV server entrypoint..." + +# Default credentials if not provided +WEBDAV_USERNAME=${WEBDAV_USERNAME:-webdav} +WEBDAV_PASSWORD=${WEBDAV_PASSWORD:-changeme} + +# Create htpasswd file with provided credentials +echo "Setting up WebDAV authentication for user: $WEBDAV_USERNAME" +htpasswd -bc /etc/nginx/auth/.htpasswd "$WEBDAV_USERNAME" "$WEBDAV_PASSWORD" + +# Ensure proper permissions on auth directory +chmod 644 /etc/nginx/auth/.htpasswd || echo "Warning: Could not set permissions on htpasswd file" + +# Ensure data directory exists +mkdir -p /data || echo "Warning: Could not create /data directory" + +# Try to set permissions on data directory (may fail if not root, that's ok) +chown -R nginx:nginx /data 2>/dev/null || echo "Warning: Could not chown /data (not running as root?)" +chmod -R 755 /data 2>/dev/null || echo "Warning: Could not chmod /data" + +# Create client body temp directory +mkdir -p /tmp/nginx_client_temp +chown -R nginx:nginx /tmp/nginx_client_temp 2>/dev/null || echo "Warning: Could not chown temp directory" + +# Test nginx configuration +echo "Testing nginx configuration..." +nginx -t + +echo "WebDAV server configuration complete!" +echo "Starting nginx..." + +# Execute the main command +exec "$@" diff --git a/applications/webdav/nginx.conf b/applications/webdav/nginx.conf new file mode 100644 index 000000000..a27d3b4cb --- /dev/null +++ b/applications/webdav/nginx.conf @@ -0,0 +1,84 @@ +user nginx; +worker_processes auto; +error_log /dev/stderr warn; +pid /var/run/nginx.pid; + +# Load DAV extension module for PROPFIND and other extended WebDAV methods +load_module /usr/lib/nginx/modules/ngx_http_dav_ext_module.so; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 0; + + server { + listen 80; + server_name _; + + # WebDAV root + location / { + root /data; + + # Enable basic authentication + auth_basic "WebDAV Access"; + auth_basic_user_file /etc/nginx/auth/.htpasswd; + + # Disable directory listing + autoindex off; + + # Enable DAV methods + dav_methods PUT DELETE MKCOL COPY MOVE; + dav_ext_methods PROPFIND OPTIONS; + + # Allow creating directories + create_full_put_path on; + + # Set permissions for uploaded files + dav_access user:rw group:rw all:r; + + # Increase client body buffer size + client_body_temp_path /tmp/nginx_client_temp; + client_max_body_size 0; + + # Allow all origins (CORS) + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range' always; + + # Handle OPTIONS method + if ($request_method = 'OPTIONS') { + return 204; + } + + # Ensure proper handling of depth header for WebDAV + set $depth_header $http_depth; + if ($depth_header = '') { + set $depth_header 'infinity'; + } + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +}