PromucFlow_constructor/deploy/docker/fs/opt/appsmith/caddy-reconfigure.mjs
Shrikant Sharat Kandula caf1d3f95c
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 13:45:04 +05:30

148 lines
3.4 KiB
JavaScript

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`
const frameAncestorsPolicy = (process.env.APPSMITH_ALLOWED_FRAME_ANCESTORS || "'self'")
.replace(/;.*$/, "")
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(" ")
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
Content-Security-Policy "frame-ancestors ${frameAncestorsPolicy}"
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
}
handle /static/* {
error 404
}
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
}
${bind} {
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()
}