feat: Implement basic auth for applications

This commit is contained in:
Pascal Klesse
2023-05-15 09:27:49 +02:00
parent f3beb5d8db
commit d14ca724e9
9 changed files with 1861 additions and 1720 deletions

View File

@@ -0,0 +1,29 @@
-- AlterTable
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -135,6 +135,8 @@ model Application {
dockerRegistryId String?
dockerRegistryImageName String?
simpleDockerfile String?
basicAuthUser String?
basicAuthPw String?
persistentStorage ApplicationPersistentStorage[]
secrets Secret[]
@@ -187,6 +189,7 @@ model ApplicationSettings {
isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false)
isHttp2 Boolean @default(false)
basicAuth Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])

View File

@@ -504,12 +504,17 @@ export async function saveApplicationSettings(
isBot,
isDBBranching,
isCustomSSL,
isHttp2
isHttp2,
basicAuth,
basicAuthUser,
basicAuthPw
} = request.body;
await prisma.application.update({
where: { id },
data: {
fqdn: isBot ? null : undefined,
basicAuthUser,
basicAuthPw,
settings: {
update: {
debug,
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
isBot,
isDBBranching,
isCustomSSL,
isHttp2
isHttp2,
basicAuth,
}
}
},

View File

@@ -43,6 +43,9 @@ export interface SaveApplicationSettings extends OnlyId {
isDBBranching: boolean;
isCustomSSL: boolean;
isHttp2: boolean;
basicAuth: boolean;
basicAuthUser: string;
basicAuthPw: string;
};
}
export interface DeleteApplication extends OnlyId {

View File

@@ -1,5 +1,5 @@
import { FastifyRequest } from 'fastify';
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
import { getTemplates } from '../../../lib/services';
import { OnlyId } from '../../../types';
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
@@ -48,20 +48,30 @@ function generateRouters(
isWWW,
isDualCerts,
isCustomSSL,
isHttp2 = false
isHttp2 = false,
basicAuth = false,
basicAuthUser = '',
basicAuthPw = ''
) {
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
const ruleWWW = `Host(\`www.${nakedDomain}\`)${
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
}`;
let http: any = {
const httpBasicAuth: any = {
basicauth: {
users: [Buffer.from(basicAuthUser + ':' + basicAuthPw).toString('base64')]
}
};
const http: any = {
entrypoints: ['web'],
rule,
service: `${serviceId}`,
priority: 2,
middlewares: []
};
let https: any = {
const https: any = {
entrypoints: ['websecure'],
rule,
service: `${serviceId}`,
@@ -71,14 +81,14 @@ function generateRouters(
},
middlewares: []
};
let httpWWW: any = {
const httpWWW: any = {
entrypoints: ['web'],
rule: ruleWWW,
service: `${serviceId}`,
priority: 2,
middlewares: []
};
let httpsWWW: any = {
const httpsWWW: any = {
entrypoints: ['websecure'],
rule: ruleWWW,
service: `${serviceId}`,
@@ -97,6 +107,10 @@ function generateRouters(
httpsWWW.middlewares.push('redirect-to-non-www');
delete https.tls;
delete httpsWWW.tls;
if (basicAuth) {
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
}
// 3. http + www only
@@ -108,6 +122,10 @@ function generateRouters(
https.middlewares.push('redirect-to-www');
delete https.tls;
delete httpsWWW.tls;
if (basicAuth) {
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
}
// 5. https + non-www only
if (isHttps && !isWWW) {
@@ -136,6 +154,10 @@ function generateRouters(
};
}
}
if (basicAuth) {
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
}
// 6. https + www only
if (isHttps && isWWW) {
@@ -145,6 +167,11 @@ function generateRouters(
http.middlewares.push('redirect-to-www');
https.middlewares.push('redirect-to-www');
}
if (basicAuth) {
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
if (isCustomSSL) {
if (isDualCerts) {
https.tls = true;
@@ -166,23 +193,23 @@ function generateRouters(
}
}
if (isHttp2) {
let http2 = {
const http2 = {
...http,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let http2WWW = {
const http2WWW = {
...httpWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2 = {
const https2 = {
...https,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2WWW = {
const https2WWW = {
...httpsWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
@@ -198,14 +225,21 @@ function generateRouters(
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
};
}
return {
const result = {
[`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
};
if (basicAuth) {
result[`${serviceId}-${pathPrefix}-basic-auth`] = { ...httpBasicAuth };
}
return result;
}
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
const traefik = {
tls: {
certificates: []
@@ -298,7 +332,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
});
}
let parsedCertificates = [];
const parsedCertificates = [];
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
@@ -369,7 +403,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
dockerComposeConfiguration,
destinationDocker,
destinationDockerId,
settings
settings,
basicAuthUser,
basicAuthPw
} = application;
if (!destinationDockerId) {
continue;
@@ -424,7 +460,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
}
continue;
}
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
const { network, id: dockerId } = destinationDocker;
if (!fqdn) {
continue;
@@ -446,7 +482,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
isWWW,
dualCerts,
isCustomSSL,
isHttp2
isHttp2,
basicAuth,
basicAuthUser,
basicAuthPw
)
};
traefik.http.services = {
@@ -482,7 +521,11 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
isHttps,
isWWW,
dualCerts,
isCustomSSL
isCustomSSL,
false,
basicAuth,
basicAuthUser,
basicAuthPw
)
};
traefik.http.services = {
@@ -542,7 +585,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
if (isDomainAndProxyConfiguration.length > 0) {
const template: any = await parseAndFindServiceTemplates(service, null, true);
const { proxy } = template.services[oneService] || found.services[oneService];
for (let configuration of proxy) {
for (const configuration of proxy) {
if (configuration.hostPort) {
continue;
}