Skip to content

Commit

Permalink
Merge pull request #43 from cmason3/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
cmason3 committed Jun 24, 2024
2 parents feb802c + 705aebe commit 3bb25d2
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 113 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## CHANGELOG

### [24.6.3] - Jun 24, 2024
- Fixed an issue with `/logs` as it didn't adhere to the default `Content-Security-Policy`
- Updated instructions for deploying JinjaFx Server as a Container using Kubernetes

### [24.6.2] - Jun 20, 2024
- The ETag hash is now across all additional headers including `Content-Type` and `Content-Security-Policy` as well as the content itself

Expand Down Expand Up @@ -315,6 +319,7 @@
### 21.11.0 - Nov 29, 2021
- Initial release

[24.6.3]: https://github.com/cmason3/jinjafx_server/compare/24.6.2...24.6.3
[24.6.2]: https://github.com/cmason3/jinjafx_server/compare/24.6.1...24.6.2
[24.6.1]: https://github.com/cmason3/jinjafx_server/compare/24.6.0...24.6.1
[24.6.0]: https://github.com/cmason3/jinjafx_server/compare/24.5.0...24.6.0
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ Once JinjaFx Server has been started with the `-s` argument then point your web
JFX_WEBLOG_KEY - specify a key to allow access to web log interface
```

For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs). The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 - please see the [/podman](https://github.com/cmason3/jinjafx_server/blob/main/podman) directory for more information about running JinjaFx as a Rootless Podman container.
For health checking purposes, if you specify the URL `/ping` then you should get an "OK" response if the JinaFx Server is up and working (these requests are omitted from the logs).

The preferred method of running the JinjaFx Server is with HAProxy in front of it as it supports TLS termination and HTTP/2 (and more recently HTTP/3 using QUIC) or using a container orchestration tool like Kubernetes - please see the [/kubernetes](/kubernetes) directory for more information about running JinjaFx using Kubernetes.

The "-r", "-s3" or "-github" arguments (mutually exclusive) allow you to specify a repository ("-r" is a local directory, "-s3" is an AWS S3 URL and "-github" is a GitHub repository) that will be used to store DataTemplates on the server via the "Get Link" and "Update Link" buttons. The generated link is guaranteed to be unique and a different link will be created every time - version 1.3.0 changed the behaviour, where previously the same link was always generated for the same DataTemplate, but this made it difficult to update DataTemplates without the link changing as it was basically a cryptographic hash of your DataTemplate. If you use an AWS S3 bucket then you will also need to provide some credentials via the two environment variables which has read and write permissions to the S3 URL.

Expand Down Expand Up @@ -124,7 +126,7 @@ Under the field the `text` key is always mandatory, but the following optional k

- `type` - if set to "password" then echo is turned off - used for inputting sensitive values

In addition to the above prompt syntax, we also support the ability to specify a custom html input form to provide greater flexibility. As JinjaFx is built on Bootstrap 5, it uses the <a href="https://getbootstrap.com/docs/5.1/components/modal/#modal-components">Bootstrap 5 Modal</a> syntax to specify what is contained in the body of your modal form. Bootstrap works on a row and column grid with each row comprising of 12 columns - you use the various "col-n" classes to specify how wide each element is.
In addition to the above prompt syntax, we also support the ability to specify a custom html input form to provide greater flexibility. As JinjaFx is built on Bootstrap 5, it uses the <a href="https://getbootstrap.com/docs/5.2/components/modal/#modal-components">Bootstrap 5 Modal</a> syntax to specify what is contained in the body of your modal form. Bootstrap works on a row and column grid with each row comprising of 12 columns - you use the various "col-n" classes to specify how wide each element is.

You can specify a custom input form using the `body` variable under `jinjafx_input` within your "vars.yml" - if this exists then whatever you have in `prompt` is ignored.

Expand Down
4 changes: 2 additions & 2 deletions jinjafx_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import re, argparse, hashlib, traceback, glob, hmac, uuid, struct, binascii, gzip, requests, ctypes, subprocess
import cmarkgfm, emoji

__version__ = '24.6.2'
__version__ = '24.6.3'

llock = threading.RLock()
rlock = threading.RLock()
Expand Down Expand Up @@ -392,7 +392,7 @@ def do_GET(self, head=False, cache=True, versioned=False):

headers = {
'X-Content-Type-Options': 'nosniff',
'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com; img-src data: *; frame-ancestors 'none'",
'Content-Security-Policy': "default-src 'self'; style-src 'self' https://cdnjs.cloudflare.com 'unsafe-inline'; script-src 'self' https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src data: *; frame-ancestors 'none'",
'Referrer-Policy': 'strict-origin-when-cross-origin'
}
etag = '"' + hashlib.sha224(repr(headers).encode('utf-8') + b'|' + r[0].encode('utf-8') + b'; ' + r[2]).hexdigest() + '"'
Expand Down
File renamed without changes.
45 changes: 45 additions & 0 deletions kubernetes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## JinjaFx Server as a Container in Kubernetes

JinjaFx Server will always be available in Docker Hub at [https://hub.docker.com/repository/docker/cmason3/jinjafx_server](https://hub.docker.com/repository/docker/cmason3/jinjafx_server) - the `latest` tag will always refer to the latest released version, although it is recommended to use explicit version tags.

The following steps will run JinjaFx Server in a container using Kubernetes Ingress - Ingress is basically the same concept as Virtual Hosting (the default Ingress uses nginx), which works with HTTP and relies on the "Host" header to direct the request to the correct container. In a Virtual Hosting scenario you would typically point different DNS A records towards the same IP, but in our example we are using a Wildcard DNS entry for our whole Kubernetes cluster, e.g:

```
*.{CLUSTER}.{DOMAIN}. 28800 IN A {HOST IP}
```

This approach also allows us to use a single wildcard TLS certificate, which covers all containers under the cluster sub-domain. The example Kubernetes manifest (`kubernetes.yml`) assumes we will be activating the Web Log as well as using a GitHub backed repository to store JinjaFx DataTemplates.

Once you have updated `kubernetes.yml` with your deployment specific values you would typically perform the following steps:

### Generate Certificate Signing Request

The following step is used to generate a CSR for your TLS certificiate. The Common Name (CN) isn't actually used as we will be using the "subjectAltName" field as it allows multiple values (you could also use something like Let's Encrypt here, but this is out of scope of this document):

```
openssl req -nodes -newkey rsa:2048 -keyout ingress.key -out ingress.csr -subj "/CN={CN}/emailAddress={emailAddress}/O={O}/L={L}/ST={ST}/C={C}" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:*.{CLUSTER}.{DOMAIN}"))
```

### Generate TLS Secret with Signed Certificate

Once you have a signed certificate you would create a Kubernetes TLS secret called `ingress-tls` using the private key and signed public certificate:

```
kubectl create secret tls ingress-tls --cert=ingress.crt --key=ingress.key
```

### Save Environment Variables as Kubernetes Secrets

To pass the GitHub Token as well as the key used for the Web Log we use Kubernetes secrets that we map into environment variables in the manifest:

```
kubectl create secret generic jinjafx --from-literal=github-token={TOKEN} --from-literal=jfx-weblog-key={KEY}
```

### Apply the Kubernetes Manifest

```
kubectl apply -f kubernetes.yml
```

If everything has worked then you should be able to point your web browser at `https://jinjafx.{CLUSTER}.{DOMAIN}` and it should present you with JinjaFx.
84 changes: 84 additions & 0 deletions kubernetes/kubernetes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jinjafx-ingress
spec:
ingressClassName: public
tls:
- hosts:
- "*.{CLUSTER}.{DOMAIN}"
secretName: ingress-tls
rules:
- host: "jinjafx.{CLUSTER}.{DOMAIN}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jinjafx-service
port:
number: 8080

---
apiVersion: v1
kind: Service
metadata:
name: jinjafx-service
spec:
selector:
app: jinjafx
ports:
- protocol: TCP
port: 8080

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jinjafx
spec:
replicas: 1
selector:
matchLabels:
app: jinjafx
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: jinjafx
spec:
containers:
- name: jinjafx
image: docker.io/cmason3/jinjafx_server:latest
args: ["-pandoc", "-github", "{OWNER}/{REPO}", "-weblog"]
ports:
- containerPort: 8080
readinessProbe:
httpGet: { scheme: HTTP, port: 8080, path: /ping }
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
livenessProbe:
httpGet: { scheme: HTTP, port: 8080, path: /ping }
periodSeconds: 30
timeoutSeconds: 5
env:
- name: TZ
value: "Europe/London"
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: jinjafx
key: github-token
- name: JFX_WEBLOG_KEY
valueFrom:
secretKeyRef:
name: jinjafx
key: jfx-weblog-key

29 changes: 0 additions & 29 deletions podman/README.md

This file was deleted.

21 changes: 0 additions & 21 deletions podman/jinjafx.container

This file was deleted.

13 changes: 13 additions & 0 deletions www/logs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
body {
color: white;
background: #000040;
}
pre {
height: 100%;
font-family: 'Fira Code', monospace;
font-size: 14px;
font-variant-ligatures: none;
white-space: pre-wrap;
word-break: break-all;
overflow-y: hidden;
}
61 changes: 2 additions & 59 deletions www/logs.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,8 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/fira_code.min.css" integrity="sha512-MbysAYimH1hH2xYzkkMHB6MqxBqfP0megxsCLknbYqHVwXTCg9IqHbk+ZP/vnhO8UEW6PaXAkKe2vQ+SWACxxA==" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="/f8555653/jinjafx.css">
<style>
body {
color: white;
background: #000040;
}
pre {
height: 100%;
font-family: 'Fira Code', monospace;
font-size: 14px;
font-variant-ligatures: none;
white-space: pre-wrap;
word-break: break-all;
overflow-y: hidden;
}
</style>
<script>
(function() {
let interval = 60;

function scroll() {
let e = document.getElementById('container');
e.scrollTop = e.scrollHeight;
}

function update() {
var xHR = new XMLHttpRequest();
xHR.open("GET", '/logs?raw', true);

xHR.onload = function() {
if (this.status == 200) {
document.getElementById('container').innerHTML = xHR.responseText;
scroll();
setTimeout(update, interval * 1000);
}
else {
document.getElementById('container').innerHTML = 'HTTP ERROR ' + this.status;
}
};

xHR.onerror = function() {
document.getElementById('container').innerHTML = 'XMLHttpRequest ERROR';
setTimeout(update, interval * 1000);
};

xHR.ontimeout = function() {
document.getElementById('container').innerHTML = 'XMLHttpRequest TIMEOUT';
setTimeout(update, interval * 1000);
};

xHR.timeout = 3000;
xHR.send();
}

window.onresize = scroll;
window.onload = function() {
update();
};
})();
</script>
<link rel="stylesheet" href="/4d93d065/logs.css">
<script src="/4f5b7922/logs.js"></script>
</head>
<body>
<pre id="container" class="p-3"></pre>
Expand Down
42 changes: 42 additions & 0 deletions www/logs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(function() {
let interval = 60;

function scroll() {
let e = document.getElementById('container');
e.scrollTop = e.scrollHeight;
}

function update() {
var xHR = new XMLHttpRequest();
xHR.open("GET", '/logs?raw', true);

xHR.onload = function() {
if (this.status == 200) {
document.getElementById('container').innerHTML = xHR.responseText;
scroll();
setTimeout(update, interval * 1000);
}
else {
document.getElementById('container').innerHTML = 'HTTP ERROR ' + this.status;
}
};

xHR.onerror = function() {
document.getElementById('container').innerHTML = 'XMLHttpRequest ERROR';
setTimeout(update, interval * 1000);
};

xHR.ontimeout = function() {
document.getElementById('container').innerHTML = 'XMLHttpRequest TIMEOUT';
setTimeout(update, interval * 1000);
};

xHR.timeout = 3000;
xHR.send();
}

window.onresize = scroll;
window.onload = function() {
update();
};
})();

0 comments on commit 3bb25d2

Please sign in to comment.