feat: add nginx, robots.txt, HTTP headers (#7683)
* feat: nginx + robots.txt * feat: minimal /health/ endpoint * ci: startupProbe for datatracker pod * ci: probe auth pod; set timeoutSeconds * feat: add CSP and other headers to nginx * fix: typo in nginx.conf * feat: split auth/dt nginx confs * test: test health endpoint * ci: auth service on port 80 We'll remove http-old (8080) in the future. * ci: rename auth container/nginx cfg
This commit is contained in:
parent
17e0f573b3
commit
18bb793b2d
|
@ -5,6 +5,7 @@ from django.conf.urls.static import static as static_url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.sitemaps import views as sitemap_views
|
from django.contrib.sitemaps import views as sitemap_views
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views import static as static_view
|
from django.views import static as static_view
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
@ -35,6 +36,7 @@ sitemaps = {
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views_search.frontpage),
|
url(r'^$', views_search.frontpage),
|
||||||
|
url(r'^health/', lambda _: HttpResponse()),
|
||||||
url(r'^accounts/', include('ietf.ietfauth.urls')),
|
url(r'^accounts/', include('ietf.ietfauth.urls')),
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^admin/docs/', include('django.contrib.admindocs.urls')),
|
url(r'^admin/docs/', include('django.contrib.admindocs.urls')),
|
||||||
|
|
|
@ -679,3 +679,12 @@ class SearchableFieldTests(TestCase):
|
||||||
self.assertTrue(changed_form.has_changed())
|
self.assertTrue(changed_form.has_changed())
|
||||||
unchanged_form = TestForm(initial={'test_field': [1]}, data={'test_field': [1]})
|
unchanged_form = TestForm(initial={'test_field': [1]}, data={'test_field': [1]})
|
||||||
self.assertFalse(unchanged_form.has_changed())
|
self.assertFalse(unchanged_form.has_changed())
|
||||||
|
|
||||||
|
|
||||||
|
class HealthTests(TestCase):
|
||||||
|
def test_health(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.get("/health/").status_code,
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,6 @@ spec:
|
||||||
- name: auth
|
- name: auth
|
||||||
image: "ghcr.io/ietf-tools/datatracker:$APP_IMAGE_TAG"
|
image: "ghcr.io/ietf-tools/datatracker:$APP_IMAGE_TAG"
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: dt-vol
|
- name: dt-vol
|
||||||
mountPath: /a
|
mountPath: /a
|
||||||
|
@ -49,6 +45,14 @@ spec:
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: dt-secrets-env
|
name: dt-secrets-env
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8000
|
||||||
|
path: /health/
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 30
|
||||||
|
timeoutSeconds: 3
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
|
@ -58,6 +62,28 @@ spec:
|
||||||
runAsUser: 1000
|
runAsUser: 1000
|
||||||
runAsGroup: 1000
|
runAsGroup: 1000
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
|
# Nginx Container
|
||||||
|
# -----------------------------------------------------
|
||||||
|
- name: nginx
|
||||||
|
image: "ghcr.io/nginxinc/nginx-unprivileged:1.27"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8080
|
||||||
|
path: /health/nginx
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: dt-cfg
|
||||||
|
mountPath: /etc/nginx/conf.d/auth.conf
|
||||||
|
subPath: nginx-auth.conf
|
||||||
|
# -----------------------------------------------------
|
||||||
# ScoutAPM Container
|
# ScoutAPM Container
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
- name: scoutapm
|
- name: scoutapm
|
||||||
|
@ -97,6 +123,9 @@ spec:
|
||||||
- name: dt-cfg
|
- name: dt-cfg
|
||||||
configMap:
|
configMap:
|
||||||
name: files-cfgmap
|
name: files-cfgmap
|
||||||
|
- name: nginx-tmp
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: "500Mi"
|
||||||
dnsPolicy: ClusterFirst
|
dnsPolicy: ClusterFirst
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
|
@ -108,9 +137,13 @@ metadata:
|
||||||
spec:
|
spec:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
ports:
|
ports:
|
||||||
- port: 8080
|
- port: 80
|
||||||
targetPort: http
|
targetPort: http
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: http
|
name: http
|
||||||
|
- port: 8080
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http-old
|
||||||
selector:
|
selector:
|
||||||
app: auth
|
app: auth
|
||||||
|
|
|
@ -24,10 +24,6 @@ spec:
|
||||||
- name: datatracker
|
- name: datatracker
|
||||||
image: "ghcr.io/ietf-tools/datatracker:$APP_IMAGE_TAG"
|
image: "ghcr.io/ietf-tools/datatracker:$APP_IMAGE_TAG"
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: dt-vol
|
- name: dt-vol
|
||||||
mountPath: /a
|
mountPath: /a
|
||||||
|
@ -49,6 +45,14 @@ spec:
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: dt-secrets-env
|
name: dt-secrets-env
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8000
|
||||||
|
path: /health/
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 30
|
||||||
|
timeoutSeconds: 3
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
|
@ -58,6 +62,28 @@ spec:
|
||||||
runAsUser: 1000
|
runAsUser: 1000
|
||||||
runAsGroup: 1000
|
runAsGroup: 1000
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
|
# Nginx Container
|
||||||
|
# -----------------------------------------------------
|
||||||
|
- name: nginx
|
||||||
|
image: "ghcr.io/nginxinc/nginx-unprivileged:1.27"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8080
|
||||||
|
path: /health/nginx
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: nginx-tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: dt-cfg
|
||||||
|
mountPath: /etc/nginx/conf.d/datatracker.conf
|
||||||
|
subPath: nginx-datatracker.conf
|
||||||
|
# -----------------------------------------------------
|
||||||
# ScoutAPM Container
|
# ScoutAPM Container
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
- name: scoutapm
|
- name: scoutapm
|
||||||
|
@ -126,6 +152,9 @@ spec:
|
||||||
- name: dt-cfg
|
- name: dt-cfg
|
||||||
configMap:
|
configMap:
|
||||||
name: files-cfgmap
|
name: files-cfgmap
|
||||||
|
- name: nginx-tmp
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: "500Mi"
|
||||||
dnsPolicy: ClusterFirst
|
dnsPolicy: ClusterFirst
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
|
|
|
@ -3,6 +3,8 @@ namePrefix: dt-
|
||||||
configMapGenerator:
|
configMapGenerator:
|
||||||
- name: files-cfgmap
|
- name: files-cfgmap
|
||||||
files:
|
files:
|
||||||
|
- nginx-auth.conf
|
||||||
|
- nginx-datatracker.conf
|
||||||
- settings_local.py
|
- settings_local.py
|
||||||
resources:
|
resources:
|
||||||
- auth.yaml
|
- auth.yaml
|
||||||
|
|
34
k8s/nginx-auth.conf
Normal file
34
k8s/nginx-auth.conf
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
server {
|
||||||
|
listen 8080 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Note that regex location matches take priority over non-regex "prefix" matches. Use regexes so that
|
||||||
|
# our deny all rule does not squelch the other locations.
|
||||||
|
location ~ ^/health/nginx$ {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/robots.txt$ {
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 200 "User-agent: *\nDisallow: /\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/accounts/create.* {
|
||||||
|
return 302 https://datatracker.ietf.org/accounts/create;
|
||||||
|
}
|
||||||
|
|
||||||
|
# n.b. (?!...) is a negative lookahead group
|
||||||
|
location ~ ^(/(?!(api/openid/|accounts/login/|accounts/logout/|accounts/reset/|person/.*/photo|group/groupmenu.json)).*) {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' data: https://datatracker.ietf.org/ https://www.ietf.org/ http://ietf.org/ https://analytics.ietf.org https://static.ietf.org; frame-ancestors 'self' ietf.org *.ietf.org meetecho.com *.meetecho.com gather.town *.gather.town";
|
||||||
|
proxy_set_header Host $${keepempty}host;
|
||||||
|
proxy_set_header Connection close;
|
||||||
|
proxy_set_header X-Request-Start "t=${msec}";
|
||||||
|
proxy_set_header X-Forwarded-For $${keepempty}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Real-IP $${keepempty}remote_addr;
|
||||||
|
proxy_pass http://localhost:8000;
|
||||||
|
}
|
||||||
|
}
|
23
k8s/nginx-datatracker.conf
Normal file
23
k8s/nginx-datatracker.conf
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
server {
|
||||||
|
listen 8080 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location /health/nginx {
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /robots.txt {
|
||||||
|
add_header Content-Type text/plain;
|
||||||
|
return 200 "User-agent: *\nDisallow: /doc/pdf/\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' data: https://datatracker.ietf.org/ https://www.ietf.org/ http://ietf.org/ https://analytics.ietf.org https://static.ietf.org; frame-ancestors 'self' ietf.org *.ietf.org meetecho.com *.meetecho.com";
|
||||||
|
proxy_set_header Host $${keepempty}host;
|
||||||
|
proxy_set_header Connection close;
|
||||||
|
proxy_set_header X-Request-Start "t=${msec}";
|
||||||
|
proxy_set_header X-Forwarded-For $${keepempty}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Real-IP $${keepempty}remote_addr;
|
||||||
|
proxy_pass http://localhost:8000;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue