feat: www <-> non-www redirection for apps

This commit is contained in:
Andras Bacsai 2022-02-13 15:04:00 +01:00
parent 3deff162bb
commit 69d3cb5dd8
5 changed files with 92 additions and 25 deletions

View File

@ -1,5 +1,5 @@
import { dev } from '$app/env'; import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import got from 'got'; import got from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
@ -50,8 +50,8 @@ export async function completeTransaction(transactionId) {
} }
export async function removeProxyConfiguration({ domain }) { export async function removeProxyConfiguration({ domain }) {
const haproxy = await haproxyInstance();
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
const haproxy = await haproxyInstance();
const backendFound = await haproxy const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`) .get(`v2/services/haproxy/configuration/backends/${domain}`)
.json(); .json();
@ -63,8 +63,32 @@ export async function removeProxyConfiguration({ domain }) {
} }
}) })
.json(); .json();
await completeTransaction(transactionId);
} }
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) {
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
}
}
await completeTransaction(transactionId);
} }
export async function forceSSLOffApplication({ domain }) { export async function forceSSLOffApplication({ domain }) {
if (!dev) { if (!dev) {
@ -124,7 +148,9 @@ export async function forceSSLOnApplication({ domain }) {
.json(); .json();
let nextRule = 0; let nextRule = 0;
if (rules.data.length > 0) { if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`)); const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) return; if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1; nextRule = rules.data[rules.data.length - 1].index + 1;
} }
@ -138,7 +164,7 @@ export async function forceSSLOnApplication({ domain }) {
json: { json: {
index: nextRule, index: nextRule,
cond: 'if', cond: 'if',
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`, cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect', type: 'redirect',
redir_type: 'scheme', redir_type: 'scheme',
redir_value: 'https', redir_value: 'https',
@ -572,3 +598,59 @@ export async function configureSimpleServiceProxyOff({ domain }) {
} catch (error) {} } catch (error) {}
return; return;
} }
export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance();
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
const transactionId = await getNextTransactionId();
try {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
let nextRule = 0;
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: `${isWWW ? 'unless' : 'if'}`,
cond_test: `{ hdr_beg(host) -i www }`,
type: 'redirect',
redir_type: 'location',
redir_value: redirectValue,
redir_code: 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
await completeTransaction(transactionId);
}
}

View File

@ -4,7 +4,7 @@ import * as buildpacks from '../buildPacks';
import * as importers from '../importers'; import * as importers from '../importers';
import { dockerInstance } from '../docker'; import { dockerInstance } from '../docker';
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common'; import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
import { configureProxyForApplication, reloadHaproxy } from '../haproxy'; import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { decrypt } from '$lib/crypto'; import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common'; import { sentry } from '$lib/common';
@ -248,6 +248,7 @@ export default async function (job) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId }); saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port }); await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId }); if (isHttps) await letsEncrypt({ domain, id: applicationId });
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId }); saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else { } else {

View File

@ -266,7 +266,7 @@
required required
/> />
<Explainer <Explainer
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>To modify the domain, you must first stop the application." text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/> />
</div> </div>
</div> </div>

View File

@ -110,25 +110,9 @@
required required
/> />
<Explainer <Explainer
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you." text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
/> />
</div> </div>
<!-- {:else}
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
<div class="col-span-2 ">
<CopyPasswordField
placeholder="eg: https://analytics.coollabs.io"
readonly={!$session.isAdmin}
name="fqdn"
id="fqdn"
bind:value={service.fqdn}
required
/>
<Explainer
text="If you specify <span class='text-green-600'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
/>
</div>
{/if} -->
</div> </div>
{#if service.type === 'plausibleanalytics'} {#if service.type === 'plausibleanalytics'}
<PlausibleAnalytics bind:service {readOnly} /> <PlausibleAnalytics bind:service {readOnly} />

View File

@ -118,7 +118,7 @@
required required
/> />
<Explainer <Explainer
text="Set the fully qualified domain name for your Coolify instance. <br>If you specify <span class='text-green-600 font-bold'>https</span>, it will be accessible only over https. <br>SSL certificate will be generated for you." text="If you specify <span class='text-green-600 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
/> />
</div> </div>
</div> </div>