mirror of
https://github.com/caprover/caprover
synced 2026-09-22 06:35:36 +00:00
Merge pull request #1154 from caprover/app-tokens
Add support for app tokens
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -617,7 +618,8 @@ class AppsDataStore {
|
||||
customNginxConfig: string,
|
||||
preDeployFunction: string,
|
||||
serviceUpdateOverride: string,
|
||||
websocketSupport: boolean
|
||||
websocketSupport: boolean,
|
||||
appDeployTokenConfig: AppDeployTokenConfig
|
||||
) {
|
||||
const self = this
|
||||
let appObj: IAppDef
|
||||
@@ -692,6 +694,33 @@ class AppsDataStore {
|
||||
appObj.serviceUpdateOverride = serviceUpdateOverride
|
||||
appObj.description = description
|
||||
|
||||
appObj.appDeployTokenConfig = {
|
||||
enabled: !!appDeployTokenConfig.enabled,
|
||||
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) {
|
||||
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(
|
||||
32
|
||||
)
|
||||
}
|
||||
|
||||
if (httpAuth && httpAuth.user) {
|
||||
const newAuth: IHttpAuth = {
|
||||
user: httpAuth.user + '',
|
||||
|
||||
@@ -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<DataStore> = {}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -90,6 +90,77 @@ 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 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) {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,6 +18,10 @@ const threadLockNamespace = {} as IHashMapGeneric<boolean>
|
||||
|
||||
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) {
|
||||
|
||||
@@ -328,8 +328,24 @@ 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
|
||||
? appDeployTokenConfig.appDeployToken
|
||||
: ''
|
||||
}`.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
if (repoInfo.user) {
|
||||
repoInfo.user = repoInfo.user.trim()
|
||||
}
|
||||
@@ -381,7 +397,8 @@ router.post('/update/', function (req, res, next) {
|
||||
customNginxConfig,
|
||||
preDeployFunction,
|
||||
serviceUpdateOverride,
|
||||
websocketSupport
|
||||
websocketSupport,
|
||||
appDeployTokenConfig
|
||||
)
|
||||
.then(function () {
|
||||
Logger.d(`AppName is updated: ${appName}`)
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -157,6 +157,8 @@ let data = {
|
||||
|
||||
headerAuth: 'x-captain-auth',
|
||||
|
||||
headerAppToken: 'x-captain-app-token',
|
||||
|
||||
headerNamespace: 'x-namespace',
|
||||
|
||||
// ********************* ETC ************************
|
||||
|
||||
@@ -335,7 +335,10 @@ export default class MigrateCaptainDuckDuck {
|
||||
app.customNginxConfig,
|
||||
app.preDeployFunction,
|
||||
'',
|
||||
false
|
||||
false,
|
||||
{
|
||||
enabled: false,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 (!byteLength) {
|
||||
byteLength = 12
|
||||
}
|
||||
return crypto.randomBytes(byteLength).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
|
||||
|
||||
Reference in New Issue
Block a user