mirror of
https://github.com/weiss/ngx_http_upload.git
synced 2025-07-13 06:57:23 +00:00
Merge bf5fdf47b6779579aa5211fc532846403fd577ba into c4d3486b126e84c785b5345abb07c75a780a0ed8
This commit is contained in:
commit
18d155ac01
440
kubernetes-manifest.yaml.example
Normal file
440
kubernetes-manifest.yaml.example
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: xmpp
|
||||||
|
---
|
||||||
|
### should be adjusted also in according to actual needs
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: httpupload
|
||||||
|
namespace: xmpp
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
### should be adjusted also in according to actual needs
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: httpuploadexamplecom
|
||||||
|
namespace: xmpp
|
||||||
|
spec:
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
duration: 2160h # 90d
|
||||||
|
renewBefore: 360h # 15d
|
||||||
|
privateKey:
|
||||||
|
algorithm: RSA
|
||||||
|
encoding: PKCS1
|
||||||
|
size: 4096
|
||||||
|
rotationPolicy: Always
|
||||||
|
secretName: httpuploadexamplecom
|
||||||
|
dnsNames:
|
||||||
|
- 'http.upload.example.com'
|
||||||
|
|
||||||
|
---
|
||||||
|
### should be adjusted also in according to actual needs
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: httpupload
|
||||||
|
namespace: xmpp
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: "Host(`http.upload.example.com`)"
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: httpupload
|
||||||
|
port: 80
|
||||||
|
middlewares:
|
||||||
|
- name: ratelimit
|
||||||
|
namespace: default
|
||||||
|
- name: https-redirectscheme
|
||||||
|
namespace: default
|
||||||
|
tls:
|
||||||
|
secretName: httpuploadexamplecom # certificate name created with cert-manager
|
||||||
|
options:
|
||||||
|
|
||||||
|
---
|
||||||
|
### should be adjusted also in according to actual needs
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: httpupload-config
|
||||||
|
namespace: xmpp
|
||||||
|
data:
|
||||||
|
nginx.conf: |
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
load_module modules/ngx_http_perl_module.so;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log notice;
|
||||||
|
pid /nginx/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
client_body_temp_path /nginx/client_temp;
|
||||||
|
proxy_temp_path /nginx/proxy_temp_path;
|
||||||
|
fastcgi_temp_path /nginx/fastcgi_temp;
|
||||||
|
uwsgi_temp_path /nginx/uwsgi_temp;
|
||||||
|
scgi_temp_path /nginx/scgi_temp;
|
||||||
|
|
||||||
|
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 /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
#perl_modules /usr/local/lib/perl5/site_perl; # Path to upload.pm.
|
||||||
|
perl_require upload.pm;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
#gzip on;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
||||||
|
default.conf: |
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
root /http-upload;
|
||||||
|
|
||||||
|
fastcgi_buffers 64 4K;
|
||||||
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
large_client_header_buffers 4 16k;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
perl upload::handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_max_body_size 100m;
|
||||||
|
}
|
||||||
|
upload.pm: |
|
||||||
|
# Nginx module to handle file uploads and downloads for ejabberd's
|
||||||
|
# mod_http_upload or Prosody's mod_http_upload_external.
|
||||||
|
|
||||||
|
# Copyright (c) 2018 Holger Weiss <holger@zedat.fu-berlin.de>
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
# purpose with or without fee is hereby granted, provided that the above
|
||||||
|
# copyright notice and this permission notice appear in all copies.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package upload;
|
||||||
|
|
||||||
|
## CONFIGURATION -----------------------------------------------------
|
||||||
|
|
||||||
|
my $external_secret = 'it-is-secret';
|
||||||
|
my $uri_prefix_components = 0;
|
||||||
|
my $file_mode = 0640;
|
||||||
|
my $dir_mode = 0750;
|
||||||
|
my %custom_headers = (
|
||||||
|
'Access-Control-Allow-Origin' => '*',
|
||||||
|
'Access-Control-Allow-Methods' => 'OPTIONS, HEAD, GET, PUT',
|
||||||
|
'Access-Control-Allow-Headers' => 'Authorization, Content-Type',
|
||||||
|
'Access-Control-Allow-Credentials' => 'true',
|
||||||
|
);
|
||||||
|
|
||||||
|
## END OF CONFIGURATION ----------------------------------------------
|
||||||
|
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
use Carp;
|
||||||
|
use Digest::SHA qw(hmac_sha256_hex);
|
||||||
|
use Encode qw(decode :fallback_all);
|
||||||
|
use Errno qw(:POSIX);
|
||||||
|
use Fcntl;
|
||||||
|
use File::Copy;
|
||||||
|
use File::Basename;
|
||||||
|
use File::Path qw(make_path);
|
||||||
|
use nginx;
|
||||||
|
|
||||||
|
sub handle {
|
||||||
|
my $r = shift;
|
||||||
|
|
||||||
|
add_custom_headers($r);
|
||||||
|
|
||||||
|
if ($r->request_method eq 'GET' or $r->request_method eq 'HEAD') {
|
||||||
|
return handle_get_or_head($r);
|
||||||
|
} elsif ($r->request_method eq 'PUT') {
|
||||||
|
return handle_put($r);
|
||||||
|
} elsif ($r->request_method eq 'OPTIONS') {
|
||||||
|
return handle_options($r);
|
||||||
|
} else {
|
||||||
|
return DECLINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_get_or_head {
|
||||||
|
my $r = shift;
|
||||||
|
my $file_path = safe_filename($r);
|
||||||
|
|
||||||
|
if (-r $file_path and -f _) {
|
||||||
|
$r->header_out('Content-Length', -s _);
|
||||||
|
$r->allow_ranges;
|
||||||
|
$r->send_http_header;
|
||||||
|
$r->sendfile($file_path) unless $r->header_only;
|
||||||
|
return OK;
|
||||||
|
} else {
|
||||||
|
return DECLINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_put {
|
||||||
|
my $r = shift;
|
||||||
|
my $len = $r->header_in('Content-Length') or return HTTP_LENGTH_REQUIRED;
|
||||||
|
my $uri = $r->uri =~ s|(?:/[^/]+){$uri_prefix_components}/||r;
|
||||||
|
my $provided_hmac;
|
||||||
|
|
||||||
|
if (defined($r->args) and $r->args =~ /v=([[:xdigit:]]{64})/) {
|
||||||
|
$provided_hmac = $1;
|
||||||
|
} else {
|
||||||
|
$r->log_error(0, 'Rejecting upload: No auth token provided');
|
||||||
|
return HTTP_FORBIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $expected_hmac = hmac_sha256_hex("$uri $len", $external_secret);
|
||||||
|
|
||||||
|
if (not safe_eq(lc($provided_hmac), lc($expected_hmac))) {
|
||||||
|
$r->log_error(0, 'Rejecting upload: Invalid auth token');
|
||||||
|
return HTTP_FORBIDDEN;
|
||||||
|
}
|
||||||
|
if (not $r->has_request_body(\&handle_put_body)) {
|
||||||
|
$r->log_error(0, 'Rejecting upload: No data provided');
|
||||||
|
return HTTP_BAD_REQUEST;
|
||||||
|
}
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_put_body {
|
||||||
|
my $r = shift;
|
||||||
|
my $file_path = safe_filename($r);
|
||||||
|
my $dir_path = dirname($file_path);
|
||||||
|
|
||||||
|
make_path($dir_path, {chmod => $dir_mode, error => \my $error});
|
||||||
|
if (@$error) {
|
||||||
|
return system_error($r, "Cannot create directory $dir_path");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $body = $r->request_body;
|
||||||
|
my $body_file = $r->request_body_file;
|
||||||
|
|
||||||
|
if ($body) {
|
||||||
|
return store_body_from_buffer($r, $body, $file_path, $file_mode);
|
||||||
|
} elsif ($body_file) {
|
||||||
|
return store_body_from_file($r, $body_file, $file_path, $file_mode);
|
||||||
|
} else { # Huh?
|
||||||
|
$r->log_error(0, "Got no data to write to $file_path");
|
||||||
|
return HTTP_BAD_REQUEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store_body_from_buffer {
|
||||||
|
my ($r, $body, $dst_path, $mode) = @_;
|
||||||
|
|
||||||
|
if (sysopen(my $fh, $dst_path, O_WRONLY|O_CREAT|O_EXCL, $mode)) {
|
||||||
|
if (not binmode($fh)) {
|
||||||
|
return system_error($r, "Cannot set binary mode for $dst_path");
|
||||||
|
}
|
||||||
|
if (not syswrite($fh, $body)) {
|
||||||
|
return system_error($r, "Cannot write $dst_path");
|
||||||
|
}
|
||||||
|
if (not close($fh)) {
|
||||||
|
return system_error($r, "Cannot close $dst_path");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return system_error($r, "Cannot create $dst_path");
|
||||||
|
}
|
||||||
|
if (chmod($mode, $dst_path) != 1) {
|
||||||
|
return system_error($r, "Cannot change permissions of $dst_path");
|
||||||
|
}
|
||||||
|
return HTTP_CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub store_body_from_file {
|
||||||
|
my ($r, $src_path, $dst_path, $mode) = @_;
|
||||||
|
|
||||||
|
# We could merge this with the store_body_from_buffer() code by handing over
|
||||||
|
# the file handle created by sysopen() as the second argument to move(), but
|
||||||
|
# we want to let move() use rename() if possible.
|
||||||
|
|
||||||
|
if (-e $dst_path) {
|
||||||
|
$r->log_error(0, "Won't overwrite $dst_path");
|
||||||
|
return HTTP_CONFLICT;
|
||||||
|
}
|
||||||
|
if (not move($src_path, $dst_path)) {
|
||||||
|
return system_error($r, "Cannot move data to $dst_path");
|
||||||
|
}
|
||||||
|
if (chmod($mode, $dst_path) != 1) {
|
||||||
|
return system_error($r, "Cannot change permissions of $dst_path");
|
||||||
|
}
|
||||||
|
return HTTP_CREATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_options {
|
||||||
|
my $r = shift;
|
||||||
|
|
||||||
|
$r->header_out('Allow', 'OPTIONS, HEAD, GET, PUT');
|
||||||
|
$r->send_http_header;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_custom_headers {
|
||||||
|
my $r = shift;
|
||||||
|
|
||||||
|
while (my ($field, $value) = each(%custom_headers)) {
|
||||||
|
$r->header_out($field, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub safe_filename {
|
||||||
|
my $r = shift;
|
||||||
|
my $filename = decode('UTF-8', $r->filename, FB_DEFAULT | LEAVE_SRC);
|
||||||
|
my $uri = decode('UTF-8', $r->uri, FB_DEFAULT | LEAVE_SRC);
|
||||||
|
my $safe_uri = $uri =~ s|[^\p{Alnum}/_.-]|_|gr;
|
||||||
|
|
||||||
|
return substr($filename, 0, -length($uri)) . $safe_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub safe_eq {
|
||||||
|
my $a = shift;
|
||||||
|
my $b = shift;
|
||||||
|
my $n = length($a);
|
||||||
|
my $r = 0;
|
||||||
|
|
||||||
|
croak('safe_eq arguments differ in length') if length($b) != $n;
|
||||||
|
$r |= ord(substr($a, $_)) ^ ord(substr($b, $_)) for 0 .. $n - 1;
|
||||||
|
return $r == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub system_error {
|
||||||
|
my ($r, $msg) = @_;
|
||||||
|
|
||||||
|
$r->log_error($!, $msg);
|
||||||
|
|
||||||
|
return HTTP_FORBIDDEN if $!{EACCES};
|
||||||
|
return HTTP_CONFLICT if $!{EEXIST};
|
||||||
|
return HTTP_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
__END__
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: httpupload
|
||||||
|
namespace: xmpp
|
||||||
|
labels:
|
||||||
|
app: httpupload
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: httpupload
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: httpupload
|
||||||
|
spec:
|
||||||
|
# imagePullSecrets:
|
||||||
|
# - name: privateregistry2
|
||||||
|
subdomain: httpupload
|
||||||
|
# hostNetwork: true
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
fsGroup: 101
|
||||||
|
containers:
|
||||||
|
- name: httpupload
|
||||||
|
image: nginx:1.22-alpine-perl #privateregistry2/nginx-httpupload:test
|
||||||
|
imagePullPolicy: Always
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsUser: 101
|
||||||
|
runAsGroup: 101
|
||||||
|
runAsNonRoot: true
|
||||||
|
privileged: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: httpupload
|
||||||
|
mountPath: /http-upload
|
||||||
|
- name: httpupload-config
|
||||||
|
mountPath: /etc/nginx/nginx.conf
|
||||||
|
subPath: nginx.conf
|
||||||
|
- name: httpupload-config
|
||||||
|
mountPath: /etc/nginx/conf.d/default.conf
|
||||||
|
subPath: default.conf
|
||||||
|
- name: httpupload-config
|
||||||
|
mountPath: /usr/local/lib/perl5/site_perl/upload.pm
|
||||||
|
subPath: upload.pm
|
||||||
|
- name: tmpfs
|
||||||
|
mountPath: /nginx
|
||||||
|
subPath: nginx
|
||||||
|
- name: tmpfs
|
||||||
|
mountPath: /var/log/nginx
|
||||||
|
subPath: log
|
||||||
|
volumes:
|
||||||
|
- name: httpupload
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: httpupload
|
||||||
|
- name: httpupload-config
|
||||||
|
configMap:
|
||||||
|
name: httpupload-config
|
||||||
|
- name: tmpfs
|
||||||
|
emptyDir: {}
|
||||||
|
#medium: "Memory"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: httpupload
|
||||||
|
namespace: xmpp
|
||||||
|
labels:
|
||||||
|
app: httpupload
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: httpupload
|
Loading…
x
Reference in New Issue
Block a user