2023-12-05 05:17:36 +00:00
|
|
|
import * as fs from "fs"
|
|
|
|
|
import {dirname} from "path"
|
|
|
|
|
import {spawnSync} from "child_process"
|
|
|
|
|
import {X509Certificate} from "crypto"
|
|
|
|
|
|
|
|
|
|
const APPSMITH_CUSTOM_DOMAIN = process.env.APPSMITH_CUSTOM_DOMAIN ?? null
|
|
|
|
|
const CaddyfilePath = process.env.TMP + "/Caddyfile"
|
|
|
|
|
|
|
|
|
|
let certLocation = null
|
|
|
|
|
if (APPSMITH_CUSTOM_DOMAIN != null) {
|
|
|
|
|
try {
|
|
|
|
|
fs.accessSync("/appsmith-stacks/ssl/fullchain.pem", fs.constants.R_OK)
|
|
|
|
|
certLocation = "/appsmith-stacks/ssl"
|
|
|
|
|
} catch (_) {
|
|
|
|
|
// no custom certs, see if old certbot certs are there.
|
|
|
|
|
const letsEncryptCertLocation = "/etc/letsencrypt/live/" + APPSMITH_CUSTOM_DOMAIN
|
|
|
|
|
const fullChainPath = letsEncryptCertLocation + `/fullchain.pem`
|
|
|
|
|
try {
|
|
|
|
|
fs.accessSync(fullChainPath, fs.constants.R_OK)
|
|
|
|
|
console.log("Old Let's Encrypt cert file exists, now checking if it's expired.")
|
|
|
|
|
if (!isCertExpired(fullChainPath)) {
|
|
|
|
|
certLocation = letsEncryptCertLocation
|
|
|
|
|
}
|
|
|
|
|
} catch (_) {
|
|
|
|
|
// no certs there either, ignore.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tlsConfig = certLocation == null ? "" : `tls ${certLocation}/fullchain.pem ${certLocation}/privkey.pem`
|
|
|
|
|
|
2023-12-11 13:55:12 +00:00
|
|
|
const frameAncestorsPolicy = (process.env.APPSMITH_ALLOWED_FRAME_ANCESTORS || "'self'")
|
|
|
|
|
.replace(/;.*$/, "")
|
|
|
|
|
|
chore: Auto-fix invalid custom domain (#29550)
Defining custom domain as `https://example.com/` is invalid.
It should be just the domain, just `example.com`. But turns out a lot of
our users have the incorrect configuration, and our previous stack of
NGINX+Certbot was able to ignore this and serve without HTTPS. This PR
brings that behaviour back.
## Test performed
Have Appsmith running on an EC2 instance, and a domain `correct.com`
with an A-record pointed to this EC2 instance.
In the instance, we run Appsmith with `APPSMITH_CUSTOM_DOMAIN` set to
`wrong.com`. Caddy will obviously fail to provision the cert, and so we
expect it to accept connections on just HTTP.
So hitting `curl -i http://correct.com` produced a 200 with the HTML
response, and not a 308 with a redirect. Before the changes from this
PR, the same curl command produced a 308 with a redirect to
`https://correct.com`, which fails with a certificate error.
Next up, we run Appsmith with `APPSMITH_CUSTOM_DOMAIN` set to
`correct.com`. Caddy will succeed in provisioning a cert, and so we
expect HTTP URLs to be redirected to HTTPS.
So hitting `curl -i http://correct.com` produces a 308 redirect to
`http://correct.com` which then works fine, since Caddy now has the cert
for the domain.
2023-12-13 08:15:04 +00:00
|
|
|
const bind = [
|
|
|
|
|
// The custom domain is expected to only have the domain. So if it has protocol or trailing slash, we remove it.
|
|
|
|
|
(APPSMITH_CUSTOM_DOMAIN || "").replace(/^https?:\/\//, "").replace(/\/$/, ""),
|
|
|
|
|
// Also bind to http on 80, so that if the cert provisioning fails, we can still serve on http.
|
|
|
|
|
// But this still means that if cert provisioning is successful, http will be redirected to https.
|
|
|
|
|
":80",
|
|
|
|
|
].join(" ")
|
|
|
|
|
|
2023-12-05 05:17:36 +00:00
|
|
|
const parts = []
|
|
|
|
|
|
|
|
|
|
parts.push(`
|
|
|
|
|
{
|
|
|
|
|
admin 127.0.0.1:2019
|
|
|
|
|
persist_config off
|
|
|
|
|
acme_ca_root /etc/ssl/certs/ca-certificates.crt
|
|
|
|
|
servers {
|
|
|
|
|
trusted_proxies static 0.0.0.0/0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(file_server) {
|
|
|
|
|
file_server {
|
|
|
|
|
precompressed br gzip
|
|
|
|
|
disable_canonical_uris
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(reverse_proxy) {
|
|
|
|
|
reverse_proxy {
|
|
|
|
|
to 127.0.0.1:{args[0]}
|
|
|
|
|
header_up -Forwarded
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(all-config) {
|
|
|
|
|
log {
|
|
|
|
|
output stdout
|
|
|
|
|
}
|
|
|
|
|
skip_log /api/v1/health
|
|
|
|
|
|
|
|
|
|
header {
|
|
|
|
|
-Server
|
2023-12-11 13:55:12 +00:00
|
|
|
Content-Security-Policy "frame-ancestors ${frameAncestorsPolicy}"
|
2023-12-05 05:17:36 +00:00
|
|
|
X-Content-Type-Options "nosniff"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
request_body {
|
|
|
|
|
max_size 150MB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handle {
|
|
|
|
|
root * {$WWW_PATH}
|
|
|
|
|
try_files /loading.html /index.html
|
|
|
|
|
import file_server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
root * /opt/appsmith/editor
|
|
|
|
|
@file file
|
|
|
|
|
handle @file {
|
|
|
|
|
import file_server
|
|
|
|
|
skip_log
|
|
|
|
|
}
|
fix: Incorrect status code for missing static files (#29374)
I think the route precedence in Caddy is different when using `handle`
directive, vs when directly using the `error` directive.
This is causing the file `handle {` route, which is a catch-all route is
handling `/static/*` requests that don't have a corresponding file. This
handler however, doesn't respond with 404 status, it responds with 200
status for missing files, and render the `index.html` for our SPA
behaviour.
Now, the CDN we have on release.app.appsmith.com caches responses from
upstream when the status is 200. If it is 404, it won't cache and retry
next time. This is why it's essential that we respond with 404 for files
that don't exist, irrespective of the content of the response.
When the container is starting up, Caddy doesn't have all the
information yet, and may have responded with not-found for one of the
assets. But since this went out with 200 status, our CDN cached it, and
once the file _was_ available with Caddy, the CDN wouldn't retry ever.
This fix will ensure we get 404 status code for requests to `/static/*`
that point to files that don't exist.
2023-12-06 09:12:25 +00:00
|
|
|
|
|
|
|
|
handle /static/* {
|
|
|
|
|
error 404
|
|
|
|
|
}
|
2023-12-05 05:17:36 +00:00
|
|
|
|
|
|
|
|
handle /info {
|
|
|
|
|
root * /opt/appsmith
|
|
|
|
|
rewrite * /info.json
|
|
|
|
|
import file_server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@backend path /api/* /oauth2/* /login/*
|
|
|
|
|
handle @backend {
|
|
|
|
|
import reverse_proxy 8080
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handle /rts/* {
|
|
|
|
|
import reverse_proxy 8091
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
redir /supervisor /supervisor/
|
|
|
|
|
handle_path /supervisor/* {
|
|
|
|
|
import reverse_proxy 9001
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handle_errors {
|
|
|
|
|
respond "{err.status_code} {err.status_text}" {err.status_code}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
localhost:80 127.0.0.1:80 {
|
|
|
|
|
import all-config
|
|
|
|
|
}
|
|
|
|
|
|
chore: Auto-fix invalid custom domain (#29550)
Defining custom domain as `https://example.com/` is invalid.
It should be just the domain, just `example.com`. But turns out a lot of
our users have the incorrect configuration, and our previous stack of
NGINX+Certbot was able to ignore this and serve without HTTPS. This PR
brings that behaviour back.
## Test performed
Have Appsmith running on an EC2 instance, and a domain `correct.com`
with an A-record pointed to this EC2 instance.
In the instance, we run Appsmith with `APPSMITH_CUSTOM_DOMAIN` set to
`wrong.com`. Caddy will obviously fail to provision the cert, and so we
expect it to accept connections on just HTTP.
So hitting `curl -i http://correct.com` produced a 200 with the HTML
response, and not a 308 with a redirect. Before the changes from this
PR, the same curl command produced a 308 with a redirect to
`https://correct.com`, which fails with a certificate error.
Next up, we run Appsmith with `APPSMITH_CUSTOM_DOMAIN` set to
`correct.com`. Caddy will succeed in provisioning a cert, and so we
expect HTTP URLs to be redirected to HTTPS.
So hitting `curl -i http://correct.com` produces a 308 redirect to
`http://correct.com` which then works fine, since Caddy now has the cert
for the domain.
2023-12-13 08:15:04 +00:00
|
|
|
${bind} {
|
2023-12-05 05:17:36 +00:00
|
|
|
import all-config
|
|
|
|
|
${tlsConfig}
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
fs.mkdirSync(dirname(CaddyfilePath), { recursive: true })
|
|
|
|
|
fs.writeFileSync(CaddyfilePath, parts.join("\n"))
|
|
|
|
|
spawnSync("/opt/caddy/caddy", ["fmt", "--overwrite", CaddyfilePath])
|
|
|
|
|
spawnSync("/opt/caddy/caddy", ["reload", "--config", CaddyfilePath])
|
|
|
|
|
|
|
|
|
|
function isCertExpired(path) {
|
|
|
|
|
const cert = new X509Certificate(fs.readFileSync(path, "utf-8"))
|
|
|
|
|
console.log(path, cert)
|
|
|
|
|
return new Date(cert.validTo) < new Date()
|
|
|
|
|
}
|