Merge bf5fdf47b6
into c4d3486b12
This commit is contained in:
commit
18d155ac01
|
@ -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…
Reference in New Issue