From 31bc45985f0c92ac37326cea021eafb5347dbf5a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 18 Dec 2020 12:54:20 +0000 Subject: [PATCH] Update after testing, it is now possible to make a deployment to a self hosted environment. Some work still required, better authentication around MINIO deployment, currently the bucket is set to public read and there is no signing/verification to the upload process, also right now four different URLs are needed for the builder to connect correctly, ideally this shouldn't be the case. --- hosting/docker-compose.yml | 8 ++-- hosting/hosting.properties | 9 ++-- .../builder/src/builderStore/store/hosting.js | 2 +- .../start/BuilderSettingsModal.svelte | 6 ++- packages/server/src/api/controllers/auth.js | 4 +- .../src/api/controllers/deploy/Deployment.js | 2 +- .../src/api/controllers/deploy/index.js | 2 +- .../src/api/controllers/deploy/selfDeploy.js | 19 ++++---- .../src/api/controllers/deploy/utils.js | 1 + .../server/src/api/controllers/hosting.js | 4 +- .../src/api/controllers/static/index.js | 16 ++----- .../static/templates/BudibaseApp.svelte | 4 +- packages/server/src/environment.js | 6 +-- .../server/src/utilities/builder/hosting.js | 33 +++++++++---- packages/worker/src/api/controllers/deploy.js | 48 +++++++++---------- packages/worker/src/environment.js | 4 +- 16 files changed, 89 insertions(+), 79 deletions(-) diff --git a/hosting/docker-compose.yml b/hosting/docker-compose.yml index 7f196d4e3b..65d89d2b0f 100644 --- a/hosting/docker-compose.yml +++ b/hosting/docker-compose.yml @@ -8,14 +8,14 @@ services: ports: - "${APP_PORT}:${APP_PORT}" environment: - MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} - MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} - MINIO_URL: http://nginx-service:${MINIO_PORT} SELF_HOSTED: 1 COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT} LOGO_URL: ${LOGO_URL} PORT: ${APP_PORT} + HOSTING_URL: ${HOSTING_URL} + MINIO_PORT: ${MINIO_PORT} + JWT_SECRET: ${JWT_SECRET} depends_on: - worker-service @@ -29,7 +29,7 @@ services: PORT: ${WORKER_PORT} MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} - RAW_MINIO_URL: http://nginx-service:${MINIO_PORT} + RAW_MINIO_URL: http://nginx-service:5001 COUCH_DB_USERNAME: ${COUCH_DB_USER} COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD} RAW_COUCH_DB_URL: http://couchdb-service:5984 diff --git a/hosting/hosting.properties b/hosting/hosting.properties index c48d4cc1d8..4afdcc044c 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -4,9 +4,10 @@ COUCH_DB_PASSWORD=budibase COUCH_DB_USER=budibase WORKER_API_KEY=budibase BUDIBASE_ENVIRONMENT=PRODUCTION -HOSTING_URL="http://localhost:4001" +HOSTING_URL="http://localhost" LOGO_URL=https://logoipsum.com/logo/logo-15.svg APP_PORT=4002 -MINIO_PORT=4003 -COUCH_DB_PORT=4004 -WORKER_PORT=4006 +WORKER_PORT=4003 +MINIO_PORT=4004 +COUCH_DB_PORT=4005 +JWT_SECRET=testsecret diff --git a/packages/builder/src/builderStore/store/hosting.js b/packages/builder/src/builderStore/store/hosting.js index f626a738ae..36067773b5 100644 --- a/packages/builder/src/builderStore/store/hosting.js +++ b/packages/builder/src/builderStore/store/hosting.js @@ -17,7 +17,7 @@ export const getHostingStore = () => { const [info, urls] = await Promise.all(responses.map(resp => resp.json())) store.update(state => { state.hostingInfo = info - state.appUrl = urls.appServer + state.appUrl = urls.app return state }) return info diff --git a/packages/builder/src/components/start/BuilderSettingsModal.svelte b/packages/builder/src/components/start/BuilderSettingsModal.svelte index c734a3b297..d101369fba 100644 --- a/packages/builder/src/components/start/BuilderSettingsModal.svelte +++ b/packages/builder/src/components/start/BuilderSettingsModal.svelte @@ -39,8 +39,10 @@

{#if selfhosted} - - + + + + {/if} diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index e8bc000e03..f9c3ef945f 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -35,8 +35,8 @@ exports.authenticate = async ctx => { roleId: dbUser.roleId, version: app.version, } - // if in cloud add the user api key - if (env.CLOUD) { + // if in cloud add the user api key, unless self hosted + if (env.CLOUD && !env.SELF_HOSTED) { const { apiKey } = await getAPIKey(ctx.user.appId) payload.apiKey = apiKey } diff --git a/packages/server/src/api/controllers/deploy/Deployment.js b/packages/server/src/api/controllers/deploy/Deployment.js index 3b95652c44..4c0f0f2df9 100644 --- a/packages/server/src/api/controllers/deploy/Deployment.js +++ b/packages/server/src/api/controllers/deploy/Deployment.js @@ -37,10 +37,10 @@ class Deployment { if (!verification) { return } + this.verification = verification if (this.verification.quota) { this.quota = this.verification.quota } - this.verification = verification } getVerification() { diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index c92f5f5948..0117fd3735 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -24,7 +24,7 @@ async function checkAllDeployments(deployments) { deployment.status === DeploymentStatus.PENDING && Date.now() - deployment.updatedAt > MAX_PENDING_TIME_MS ) { - deployment.status = status + deployment.status = DeploymentStatus.FAILURE deployment.err = "Timed out" updated = true } diff --git a/packages/server/src/api/controllers/deploy/selfDeploy.js b/packages/server/src/api/controllers/deploy/selfDeploy.js index 766a1c35bc..429c4c9505 100644 --- a/packages/server/src/api/controllers/deploy/selfDeploy.js +++ b/packages/server/src/api/controllers/deploy/selfDeploy.js @@ -5,23 +5,26 @@ const { performReplication, fetchCredentials, } = require("./utils") -const { getDeploymentUrl } = require("../../../utilities/builder/hosting") -const { join } = require("path") +const { + getWorkerUrl, + getCouchUrl, + getMinioUrl, +} = require("../../../utilities/builder/hosting") exports.preDeployment = async function() { - const url = join(await getDeploymentUrl(), "api", "deploy") + const url = `${await getWorkerUrl()}/api/deploy` const json = await fetchCredentials(url, { apiKey: env.BUDIBASE_API_KEY, }) // response contains: - // couchDbSession, bucket, objectStoreSession, couchDbUrl, objectStoreUrl + // couchDbSession, bucket, objectStoreSession // set credentials here, means any time we're verified we're ready to go if (json.objectStoreSession) { AWS.config.update({ - accessKeyId: json.objectStoreSession.AccessKeyId, - secretAccessKey: json.objectStoreSession.SecretAccessKey, + accessKeyId: json.objectStoreSession.accessKeyId, + secretAccessKey: json.objectStoreSession.secretAccessKey, }) } @@ -36,7 +39,7 @@ exports.deploy = async function(deployment) { const appId = deployment.getAppId() const verification = deployment.getVerification() const objClient = new AWS.S3({ - endpoint: verification.objectStoreUrl, + endpoint: await getMinioUrl(), s3ForcePathStyle: true, // needed with minio? signatureVersion: "v4", params: { @@ -54,6 +57,6 @@ exports.replicateDb = async function(deployment) { return performReplication( appId, verification.couchDbSession, - verification.couchDbUrl + await getCouchUrl() ) } diff --git a/packages/server/src/api/controllers/deploy/utils.js b/packages/server/src/api/controllers/deploy/utils.js index 8e00a7c5a5..87030a82e7 100644 --- a/packages/server/src/api/controllers/deploy/utils.js +++ b/packages/server/src/api/controllers/deploy/utils.js @@ -3,6 +3,7 @@ const sanitize = require("sanitize-s3-objectkey") const { walkDir } = require("../../../utilities") const { join } = require("../../../utilities/centralPath") const { budibaseAppsDir } = require("../../../utilities/budibaseDir") +const fetch = require("node-fetch") const PouchDB = require("../../../db") const CouchDB = require("pouchdb") diff --git a/packages/server/src/api/controllers/hosting.js b/packages/server/src/api/controllers/hosting.js index 351290ca23..ce4ff98c69 100644 --- a/packages/server/src/api/controllers/hosting.js +++ b/packages/server/src/api/controllers/hosting.js @@ -3,7 +3,7 @@ const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") const { getHostingInfo, HostingTypes, - getAppServerUrl, + getAppUrl, } = require("../../utilities/builder/hosting") exports.fetchInfo = async ctx => { @@ -30,6 +30,6 @@ exports.fetch = async ctx => { exports.fetchUrls = async ctx => { ctx.body = { - appServer: await getAppServerUrl(ctx.appId), + app: await getAppUrl(ctx.appId), } } diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index 468a896a96..8916758d20 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -17,17 +17,10 @@ const setBuilderToken = require("../../../utilities/builder/setBuilderToken") const fileProcessor = require("../../../utilities/fileProcessor") const env = require("../../../environment") -function appServerUrl(appId) { - if (env.SELF_HOSTED) { - return env.HOSTING_URL - } else { - return `https://${appId}.app.budi.live` - } -} - function objectStoreUrl() { if (env.SELF_HOSTED) { - return env.MINIO_URL + // TODO: need a better way to handle this, probably reverse proxy + return `${env.HOSTING_URL}:${env.MINIO_PORT}/app-assets/assets` } else { return "https://cdn.app.budi.live/assets" } @@ -164,7 +157,7 @@ exports.serveApp = async function(ctx) { title: appInfo.name, production: env.CLOUD, appId: ctx.params.appId, - appServerUrl: appServerUrl(ctx.params.appId), + objectStoreUrl: objectStoreUrl(ctx.params.appId), }) const template = handlebars.compile( @@ -232,8 +225,7 @@ exports.serveComponentLibrary = async function(ctx) { } const S3_URL = encodeURI( join( - appServerUrl(appId), - "assets", + objectStoreUrl(appId), componentLib, ctx.query.library, "dist", diff --git a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte index 0729737a44..818b6bbd45 100644 --- a/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte +++ b/packages/server/src/api/controllers/static/templates/BudibaseApp.svelte @@ -4,11 +4,11 @@ export let appId export let production - export let appServerUrl + export let objectStoreUrl function publicPath(path) { if (production) { - return `${appServerUrl}/assets/${appId}/${path}` + return `${objectStoreUrl}/${appId}/${path}` } return `/assets/${path}` diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 19d3cf1c57..a06d5ea23c 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -37,10 +37,8 @@ module.exports = { DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES, // self hosting features - MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, - MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - MINIO_URL: process.MINIO_URL, - HOSTING_URL: process.HOSTING_URL, + HOSTING_URL: process.env.HOSTING_URL, + MINIO_PORT: process.env.MINIO_PORT, LOGO_URL: process.env.LOGO_URL, _set(key, value) { process.env[key] = value diff --git a/packages/server/src/utilities/builder/hosting.js b/packages/server/src/utilities/builder/hosting.js index d1f8632b5d..0e5e07b26f 100644 --- a/packages/server/src/utilities/builder/hosting.js +++ b/packages/server/src/utilities/builder/hosting.js @@ -1,6 +1,5 @@ const CouchDB = require("../../db") const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") -const { join } = require("path") function getProtocol(hostingInfo) { return hostingInfo.useHttps ? "https://" : "http://" @@ -23,8 +22,10 @@ exports.getHostingInfo = async () => { doc = { _id: HOSTING_DOC, type: exports.HostingTypes.CLOUD, - appServerUrl: "app.budi.live", - deploymentServerUrl: "", + appUrl: "app.budi.live", + workerUrl: "", + minioUrl: "", + couchUrl: "", templatesUrl: "prod-budi-templates.s3-eu-west-1.amazonaws.com", useHttps: true, } @@ -32,22 +33,34 @@ exports.getHostingInfo = async () => { return doc } -exports.getAppServerUrl = async appId => { +exports.getAppUrl = async appId => { const hostingInfo = await exports.getHostingInfo() const protocol = getProtocol(hostingInfo) let url if (hostingInfo.type === "cloud") { - url = `${protocol}${appId}.${hostingInfo.appServerUrl}` + url = `${protocol}${appId}.${hostingInfo.appUrl}` } else { - url = `${protocol}${hostingInfo.appServerUrl}` + url = `${protocol}${hostingInfo.appUrl}` } return url } -exports.getDeploymentUrl = async () => { +exports.getWorkerUrl = async () => { const hostingInfo = await exports.getHostingInfo() const protocol = getProtocol(hostingInfo) - return `${protocol}${hostingInfo.deploymentServerUrl}` + return `${protocol}${hostingInfo.workerUrl}` +} + +exports.getMinioUrl = async () => { + const hostingInfo = await exports.getHostingInfo() + const protocol = getProtocol(hostingInfo) + return `${protocol}${hostingInfo.minioUrl}` +} + +exports.getCouchUrl = async () => { + const hostingInfo = await exports.getHostingInfo() + const protocol = getProtocol(hostingInfo) + return `${protocol}${hostingInfo.couchUrl}` } exports.getTemplatesUrl = async (appId, type, name) => { @@ -55,9 +68,9 @@ exports.getTemplatesUrl = async (appId, type, name) => { const protocol = getProtocol(hostingInfo) let path if (type && name) { - path = join("templates", type, `${name}.tar.gz`) + path = `templates/type/${name}.tar.gz` } else { path = "manifest.json" } - return join(`${protocol}${hostingInfo.templatesUrl}`, path) + return `${protocol}${hostingInfo.templatesUrl}/${path}` } diff --git a/packages/worker/src/api/controllers/deploy.js b/packages/worker/src/api/controllers/deploy.js index cdff0dadcb..e2cf3f6a6e 100644 --- a/packages/worker/src/api/controllers/deploy.js +++ b/packages/worker/src/api/controllers/deploy.js @@ -5,11 +5,23 @@ const AWS = require("aws-sdk") const APP_BUCKET = "app-assets" // this doesn't matter in self host const REGION = "eu-west-1" +const PUBLIC_READ_POLICY = { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: "*", + Action: "s3:GetObject", + Resource: `arn:aws:s3:::${APP_BUCKET}/*`, + } + ] +} async function getCouchSession() { // fetch session token for the api user const session = await got.post(`${env.RAW_COUCH_DB_URL}/_session`, { responseType: "json", + credentials: "include", json: { username: env.COUCH_DB_USERNAME, password: env.COUCH_DB_PASSWORD, @@ -38,37 +50,25 @@ async function getMinioSession() { }) // make sure the bucket exists try { - await objClient.headBucket({ Bucket: APP_BUCKET }).promise() + await objClient.headBucket({ + Bucket: APP_BUCKET + }).promise() + await objClient.putBucketPolicy({ + Bucket: APP_BUCKET, + Policy: JSON.stringify(PUBLIC_READ_POLICY), + }).promise() } catch (err) { // bucket doesn't exist create it if (err.statusCode === 404) { - await objClient.createBucket({ Bucket: APP_BUCKET }).promise() + await objClient.createBucket({ + Bucket: APP_BUCKET, + }).promise() } else { throw err } } - // TODO: this doesn't seem to work get an error - // TODO: Generating temporary credentials not allowed for this request. - // TODO: this should work based on minio documentation - // const sts = new AWS.STS({ - // endpoint: env.RAW_MINIO_URL, - // region: REGION, - // s3ForcePathStyle: true, - // }) - // // NOTE: In the following commands RoleArn and RoleSessionName are not meaningful for MinIO - // const params = { - // DurationSeconds: 3600, - // ExternalId: "123ABC", - // Policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}', - // RoleArn: 'arn:xxx:xxx:xxx:xxxx', - // RoleSessionName: 'anything', - // }; - // const assumedRole = await sts.assumeRole(params).promise(); - // if (!assumedRole) { - // throw "Unable to get access to object store." - // } - // return assumedRole.Credentials // TODO: need to do something better than this + // Ideally want to send back some pre-signed URLs for files that are to be uploaded return { accessKeyId: env.MINIO_ACCESS_KEY, secretAccessKey: env.MINIO_SECRET_KEY, @@ -80,7 +80,5 @@ exports.deploy = async ctx => { couchDbSession: await getCouchSession(), bucket: APP_BUCKET, objectStoreSession: await getMinioSession(), - couchDbUrl: env.RAW_COUCH_DB_URL, - objectStoreUrl: env.RAW_MINIO_URL, } } diff --git a/packages/worker/src/environment.js b/packages/worker/src/environment.js index fee1d3be04..ce0f174eb9 100644 --- a/packages/worker/src/environment.js +++ b/packages/worker/src/environment.js @@ -4,10 +4,12 @@ module.exports = { PORT: process.env.PORT, MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, - RAW_MINIO_URL: process.env.RAW_MINIO_URL, COUCH_DB_USERNAME: process.env.COUCH_DB_USERNAME, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, RAW_COUCH_DB_URL: process.env.RAW_COUCH_DB_URL, + RAW_MINIO_URL: process.env.RAW_MINIO_URL, + COUCH_DB_PORT: process.env.COUCH_DB_PORT, + MINIO_PORT: process.env.MINIO_PORT, _set(key, value) { process.env[key] = value module.exports[key] = value