From 7551a0d1e43c8189ece605f100f2420bc27b877b Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 10:19:09 -0700 Subject: [PATCH 1/8] Add support for app tokens --- src/datastore/AppsDataStore.ts | 17 ++++- src/injection/Injector.ts | 69 +++++++++++++++++++ src/models/AppDefinition.ts | 7 +- src/routes/user/UserRouter.ts | 4 ++ .../apps/appdefinition/AppDefinitionRouter.ts | 12 ++++ src/user/ServiceManager.ts | 6 +- src/utils/MigrateCaptainDuckDuck.ts | 5 +- src/utils/Utils.ts | 8 +++ 8 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/datastore/AppsDataStore.ts b/src/datastore/AppsDataStore.ts index 81520a9..7a47c23 100644 --- a/src/datastore/AppsDataStore.ts +++ b/src/datastore/AppsDataStore.ts @@ -617,7 +617,8 @@ class AppsDataStore { customNginxConfig: string, preDeployFunction: string, serviceUpdateOverride: string, - websocketSupport: boolean + websocketSupport: boolean, + appDeployTokenConfig: AppDeployTokenConfig ) { const self = this let appObj: IAppDef @@ -692,6 +693,20 @@ class AppsDataStore { appObj.serviceUpdateOverride = serviceUpdateOverride appObj.description = description + appObj.appDeployTokenConfig = { + enabled: !!appDeployTokenConfig.enabled, + appDeployToken: appDeployTokenConfig.appDeployToken, + } + + if (!appObj.appDeployTokenConfig.enabled) { + appObj.appDeployTokenConfig.appDeployToken = undefined + } else if (!appObj.appDeployTokenConfig.appDeployToken) { + // App is supposed to have a token, but it doesn't have one yet. The first time use case. + appObj.appDeployTokenConfig.appDeployToken = Utils.generateRandomString( + 12 + ) + } + if (httpAuth && httpAuth.user) { const newAuth: IHttpAuth = { user: httpAuth.user + '', diff --git a/src/injection/Injector.ts b/src/injection/Injector.ts index cc0bb69..70839c3 100644 --- a/src/injection/Injector.ts +++ b/src/injection/Injector.ts @@ -90,6 +90,75 @@ export function injectUser() { } } +/** + * A pseudo user injection. Only used for build triggers. Can only trigger certain actions. + */ +export function injectUserForBuildTrigger() { + return function (req: Request, res: Response, next: NextFunction) { + const token = req.query.token as string + const namespace = req.query.namespace as string + const appName = req.params.appName as string + + if (!token || !namespace || !appName) { + Logger.e( + 'Trigger app build is called with no token/namespace/appName' + ) + next() + return + } + + const dataStore = DataStoreProvider.getDataStore(namespace) + let app: IAppDef | undefined = undefined + + Promise.resolve() + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName) + }) + .then(function (appFound) { + app = appFound + + const tokenMatches = + app?.appDeployTokenConfig?.enabled && + app.appDeployTokenConfig.appDeployToken === token + + if (!tokenMatches) { + Logger.e('Token mismatch for app build') + next() + return + } + + const datastore = DataStoreProvider.getDataStore(namespace) + + const serviceManager = ServiceManager.get( + namespace, + Authenticator.getAuthenticator(namespace), + datastore, + dockerApi, + CaptainManager.get().getLoadBalanceManager(), + CaptainManager.get().getDomainResolveChecker() + ) + + const user: UserModel.UserInjected = { + namespace: namespace, + dataStore: datastore, + serviceManager: serviceManager, + initialized: serviceManager.isInited(), + } + + res.locals.user = user + res.locals.app = app + res.locals.appName = appName + + next() + }) + .catch(function (error) { + Logger.e(error) + res.locals.app = undefined + next() + }) + } +} + /** * A pseudo user injection. Only used for webhooks. Can only trigger certain actions. */ diff --git a/src/models/AppDefinition.ts b/src/models/AppDefinition.ts index e60dc00..cb84984 100644 --- a/src/models/AppDefinition.ts +++ b/src/models/AppDefinition.ts @@ -68,8 +68,8 @@ interface IAppDefinitionBase { ports: IAppPort[] volumes: IAppVolume[] envVars: IAppEnvVar[] - versions: IAppVersion[] + appDeployTokenConfig?: AppDeployTokenConfig } interface IHttpAuth { @@ -78,6 +78,11 @@ interface IHttpAuth { passwordHashed?: string } +interface AppDeployTokenConfig { + enabled: boolean + appDeployToken?: string +} + interface IAppDef extends IAppDefinitionBase { appPushWebhook?: { tokenVersion: string diff --git a/src/routes/user/UserRouter.ts b/src/routes/user/UserRouter.ts index 08d4318..8cedbae 100644 --- a/src/routes/user/UserRouter.ts +++ b/src/routes/user/UserRouter.ts @@ -18,6 +18,10 @@ const threadLockNamespace = {} as IHashMapGeneric router.use('/apps/webhooks/', Injector.injectUserForWebhook()) +// Only for POST request to build the image +// Ensure that it doesn't allow for GET requests etc. +router.post('/apps/appData/:appName/', Injector.injectUserForBuildTrigger()) + router.use(Injector.injectUser()) router.use(function (req, res, next) { diff --git a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts index 370b0e4..aa83931 100644 --- a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts +++ b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts @@ -328,8 +328,20 @@ router.post('/update/', function (req, res, next) { let serviceUpdateOverride = req.body.serviceUpdateOverride || '' let containerHttpPort = Number(req.body.containerHttpPort) || 80 let httpAuth = req.body.httpAuth + let appDeployTokenConfig = req.body.appDeployTokenConfig as + | AppDeployTokenConfig + | undefined let description = req.body.description || '' + if (!appDeployTokenConfig) { + appDeployTokenConfig = { enabled: false } + } else { + appDeployTokenConfig = { + enabled: !!appDeployTokenConfig.enabled, + appDeployToken: `${appDeployTokenConfig.appDeployToken}`.trim(), + } + } + if (repoInfo.user) { repoInfo.user = repoInfo.user.trim() } diff --git a/src/user/ServiceManager.ts b/src/user/ServiceManager.ts index 0d983c7..3752ad1 100644 --- a/src/user/ServiceManager.ts +++ b/src/user/ServiceManager.ts @@ -664,7 +664,8 @@ class ServiceManager { customNginxConfig: string, preDeployFunction: string, serviceUpdateOverride: string, - websocketSupport: boolean + websocketSupport: boolean, + appDeployTokenConfig: AppDeployTokenConfig ) { const self = this const dataStore = this.dataStore @@ -783,7 +784,8 @@ class ServiceManager { customNginxConfig, preDeployFunction, serviceUpdateOverride, - websocketSupport + websocketSupport, + appDeployTokenConfig ) }) .then(function () { diff --git a/src/utils/MigrateCaptainDuckDuck.ts b/src/utils/MigrateCaptainDuckDuck.ts index 27a05ec..bc94395 100644 --- a/src/utils/MigrateCaptainDuckDuck.ts +++ b/src/utils/MigrateCaptainDuckDuck.ts @@ -335,7 +335,10 @@ export default class MigrateCaptainDuckDuck { app.customNginxConfig, app.preDeployFunction, '', - false + false, + { + enabled: false, + } ) }) }) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 7a23735..6acaf6e 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,3 +1,4 @@ +import * as crypto from 'crypto' import { remove } from 'fs-extra' import * as yaml from 'yaml' import Logger from './Logger' @@ -10,6 +11,13 @@ export default class Utils { return input } + static generateRandomString(byteLength?: number) { + if (!length) { + length = 12 + } + return crypto.randomBytes(length).toString('hex') + } + static isValidIp(ip: string) { return /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( ip From ae024e661976b542dbcc2d7f321822f99337110a Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 10:20:53 -0700 Subject: [PATCH 2/8] Increasing length of token --- src/datastore/AppsDataStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datastore/AppsDataStore.ts b/src/datastore/AppsDataStore.ts index 7a47c23..8a39b4f 100644 --- a/src/datastore/AppsDataStore.ts +++ b/src/datastore/AppsDataStore.ts @@ -703,7 +703,7 @@ class AppsDataStore { } else if (!appObj.appDeployTokenConfig.appDeployToken) { // App is supposed to have a token, but it doesn't have one yet. The first time use case. appObj.appDeployTokenConfig.appDeployToken = Utils.generateRandomString( - 12 + 32 ) } From dd72012c7bd0a30f6c014c5da257d007fbfb434e Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 10:22:55 -0700 Subject: [PATCH 3/8] Fixed build --- src/routes/user/apps/appdefinition/AppDefinitionRouter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts index aa83931..860a4e6 100644 --- a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts +++ b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts @@ -393,7 +393,8 @@ router.post('/update/', function (req, res, next) { customNginxConfig, preDeployFunction, serviceUpdateOverride, - websocketSupport + websocketSupport, + appDeployTokenConfig ) .then(function () { Logger.d(`AppName is updated: ${appName}`) From 3efc73eccaf44af31381a1c232421f159e51f101 Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 14:30:32 -0700 Subject: [PATCH 4/8] More data cleaning --- src/datastore/AppsDataStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datastore/AppsDataStore.ts b/src/datastore/AppsDataStore.ts index 8a39b4f..c804e10 100644 --- a/src/datastore/AppsDataStore.ts +++ b/src/datastore/AppsDataStore.ts @@ -695,7 +695,7 @@ class AppsDataStore { appObj.appDeployTokenConfig = { enabled: !!appDeployTokenConfig.enabled, - appDeployToken: appDeployTokenConfig.appDeployToken, + appDeployToken: `${appDeployTokenConfig.appDeployToken}`, } if (!appObj.appDeployTokenConfig.enabled) { From d43e33583699f573795622737fadcd397b5e264c Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 21:52:27 -0700 Subject: [PATCH 5/8] More data cleaning --- src/datastore/AppsDataStore.ts | 16 +++++++++++++++- .../apps/appdefinition/AppDefinitionRouter.ts | 6 +++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/datastore/AppsDataStore.ts b/src/datastore/AppsDataStore.ts index c804e10..48349c1 100644 --- a/src/datastore/AppsDataStore.ts +++ b/src/datastore/AppsDataStore.ts @@ -5,6 +5,7 @@ import Authenticator from '../user/Authenticator' import ApacheMd5 from '../utils/ApacheMd5' import CaptainConstants from '../utils/CaptainConstants' import CaptainEncryptor from '../utils/Encryptor' +import Logger from '../utils/Logger' import Utils from '../utils/Utils' import configstore = require('configstore') @@ -695,7 +696,20 @@ class AppsDataStore { appObj.appDeployTokenConfig = { enabled: !!appDeployTokenConfig.enabled, - appDeployToken: `${appDeployTokenConfig.appDeployToken}`, + appDeployToken: `${ + appDeployTokenConfig.appDeployToken + ? appDeployTokenConfig.appDeployToken + : '' + }`, + } + + if ( + appObj.appDeployTokenConfig.appDeployToken === + 'undefined' || + appObj.appDeployTokenConfig.appDeployToken === 'null' + ) { + appObj.appDeployTokenConfig = { enabled: false } + Logger.e('Bad values in the token') } if (!appObj.appDeployTokenConfig.enabled) { diff --git a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts index 860a4e6..980698f 100644 --- a/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts +++ b/src/routes/user/apps/appdefinition/AppDefinitionRouter.ts @@ -338,7 +338,11 @@ router.post('/update/', function (req, res, next) { } else { appDeployTokenConfig = { enabled: !!appDeployTokenConfig.enabled, - appDeployToken: `${appDeployTokenConfig.appDeployToken}`.trim(), + appDeployToken: `${ + appDeployTokenConfig.appDeployToken + ? appDeployTokenConfig.appDeployToken + : '' + }`.trim(), } } From e55d44a99f1e1d26790b111f6659cba439b613b9 Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 25 Jul 2021 21:54:59 -0700 Subject: [PATCH 6/8] Fixed build --- src/utils/Utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 6acaf6e..924780b 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -12,10 +12,10 @@ export default class Utils { } static generateRandomString(byteLength?: number) { - if (!length) { - length = 12 + if (!byteLength) { + byteLength = 12 } - return crypto.randomBytes(length).toString('hex') + return crypto.randomBytes(byteLength).toString('hex') } static isValidIp(ip: string) { From 21ecc9212985b07f5b3009d86cb1dd96f80bbdf3 Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 1 Aug 2021 16:45:20 -0700 Subject: [PATCH 7/8] Limiting namepace --- src/datastore/DataStoreProvider.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/datastore/DataStoreProvider.ts b/src/datastore/DataStoreProvider.ts index be25153..38004bf 100644 --- a/src/datastore/DataStoreProvider.ts +++ b/src/datastore/DataStoreProvider.ts @@ -2,6 +2,8 @@ * Created by kasra on 27/06/17. */ +import ApiStatusCodes from '../api/ApiStatusCodes' +import CaptainConstants from '../utils/CaptainConstants' import DataStore from './DataStore' const dataStoreCache: IHashMapGeneric = {} @@ -12,6 +14,13 @@ export default { throw new Error('NameSpace is empty') } + if (namespace !== CaptainConstants.rootNameSpace) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + 'Namespace unknown' + ) + } + if (!dataStoreCache[namespace]) { dataStoreCache[namespace] = new DataStore(namespace) } From 7f52fd5dd0654fbf0c367d8a63e573e2c30a5480 Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sun, 1 Aug 2021 17:52:48 -0700 Subject: [PATCH 8/8] Moved to headers --- src/injection/Injector.ts | 6 ++++-- src/utils/CaptainConstants.ts | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/injection/Injector.ts b/src/injection/Injector.ts index 70839c3..4abaa25 100644 --- a/src/injection/Injector.ts +++ b/src/injection/Injector.ts @@ -95,8 +95,10 @@ export function injectUser() { */ export function injectUserForBuildTrigger() { return function (req: Request, res: Response, next: NextFunction) { - const token = req.query.token as string - const namespace = req.query.namespace as string + const locals = res.locals + + const token = req.header(CaptainConstants.headerAppToken) as string + const namespace = locals.namespace const appName = req.params.appName as string if (!token || !namespace || !appName) { diff --git a/src/utils/CaptainConstants.ts b/src/utils/CaptainConstants.ts index 578a350..7c9f42e 100644 --- a/src/utils/CaptainConstants.ts +++ b/src/utils/CaptainConstants.ts @@ -157,6 +157,8 @@ let data = { headerAuth: 'x-captain-auth', + headerAppToken: 'x-captain-app-token', + headerNamespace: 'x-namespace', // ********************* ETC ************************