Prosody: renew self signed certificates periodically
This commit is contained in:
parent
b4dabfeeb9
commit
a87a622cba
116
server/lib/prosody/certificates.ts
Normal file
116
server/lib/prosody/certificates.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
|
import type { ProsodyConfig } from './config'
|
||||||
|
import { isDebugMode } from '../debug'
|
||||||
|
import { prosodyCtl, reloadProsody } from './ctl'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
interface Renew {
|
||||||
|
timer: NodeJS.Timer
|
||||||
|
}
|
||||||
|
let renew: Renew | undefined
|
||||||
|
|
||||||
|
function _filePathToTest (options: RegisterServerOptions, config: ProsodyConfig): string {
|
||||||
|
return path.resolve(config.paths.certs, config.host + '.crt')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureProsodyCertificates (options: RegisterServerOptions, config: ProsodyConfig): Promise<void> {
|
||||||
|
if (config.certificates !== 'generate-self-signed') { return }
|
||||||
|
const logger = options.peertubeHelpers.logger
|
||||||
|
logger.info('Prosody needs certicicates, checking if certificates are okay...')
|
||||||
|
|
||||||
|
const prosodyDomain = config.host
|
||||||
|
const filepath = _filePathToTest(options, config)
|
||||||
|
if (fs.existsSync(filepath)) {
|
||||||
|
logger.info(`The certificate ${filepath} exists, no need to generate it`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using: prososyctl --config /.../prosody.cfg.lua cert generate prosodyDomain.tld
|
||||||
|
await prosodyCtl(options, 'cert', {
|
||||||
|
additionalArgs: ['generate', prosodyDomain],
|
||||||
|
yesMode: true,
|
||||||
|
stdErrFilter: (data) => {
|
||||||
|
// For an unknow reason, `prosodyctl cert generate` outputs openssl data on stderr...
|
||||||
|
// So we filter these logs.
|
||||||
|
if (data.match(/Generating \w+ private key/)) { return false }
|
||||||
|
if (data.match(/^[.+o*\n]*$/m)) { return false }
|
||||||
|
if (data.match(/e is \d+/)) { return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renewCheckSelfSigned (options: RegisterServerOptions, config: ProsodyConfig): Promise<void> {
|
||||||
|
const logger = options.peertubeHelpers.logger
|
||||||
|
// We have to check if the self signed certificate is still valid.
|
||||||
|
// Prosodyctl generated certificates are valid 365 days.
|
||||||
|
// We will renew it every 10 months (and every X minutes in debug mode)
|
||||||
|
|
||||||
|
const renewEvery = isDebugMode(options) ? 5 * 60000 : 3600000 * 24 * 30 * 10
|
||||||
|
// getting the file date...
|
||||||
|
const filepath = _filePathToTest(options, config)
|
||||||
|
if (!fs.existsSync(filepath)) {
|
||||||
|
logger.error('Missing certificate file: ' + filepath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const stat = fs.statSync(filepath)
|
||||||
|
const age = (new Date()).getTime() - stat.mtimeMs
|
||||||
|
if (age <= renewEvery) {
|
||||||
|
logger.debug(`The age of the certificate ${filepath} is ${age}ms, which is less than the period ${renewEvery}ms`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.info(`The age of the certificate ${filepath} is ${age}ms, which is more than the period ${renewEvery}ms`)
|
||||||
|
await ensureProsodyCertificates(options, config)
|
||||||
|
await reloadProsody(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renewCheck (options: RegisterServerOptions, config: ProsodyConfig): Promise<void> {
|
||||||
|
if (config.certificates === 'generate-self-signed') {
|
||||||
|
return renewCheckSelfSigned(options, config)
|
||||||
|
}
|
||||||
|
throw new Error('Unknown value for config.certificates')
|
||||||
|
}
|
||||||
|
|
||||||
|
function startProsodyCertificatesRenewCheck (options: RegisterServerOptions, config: ProsodyConfig): void {
|
||||||
|
const logger = options.peertubeHelpers.logger
|
||||||
|
|
||||||
|
if (!config.certificates) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugMode = isDebugMode(options)
|
||||||
|
// check every day (or every minutes in debug mode)
|
||||||
|
const checkInterval = debugMode ? 60000 : 3600000 * 24
|
||||||
|
|
||||||
|
if (renew) {
|
||||||
|
stopProsodyCertificatesRenewCheck(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Starting Prosody Certificates Renew Check')
|
||||||
|
renewCheck(options, config).then(() => {}, () => {})
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
logger.debug('Checking if Prosody certificates need to be renewed')
|
||||||
|
renewCheck(options, config).then(() => {}, () => {})
|
||||||
|
}, checkInterval)
|
||||||
|
|
||||||
|
renew = {
|
||||||
|
timer: timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopProsodyCertificatesRenewCheck (options: RegisterServerOptions): void {
|
||||||
|
const logger = options.peertubeHelpers.logger
|
||||||
|
if (renew === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.info('Stoping Prosody Certificates Renew Check')
|
||||||
|
clearInterval(renew.timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ensureProsodyCertificates,
|
||||||
|
startProsodyCertificatesRenewCheck,
|
||||||
|
stopProsodyCertificatesRenewCheck
|
||||||
|
}
|
@ -87,6 +87,8 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProsodyConfigCertificates = false | 'generate-self-signed'
|
||||||
|
|
||||||
interface ProsodyConfig {
|
interface ProsodyConfig {
|
||||||
content: string
|
content: string
|
||||||
paths: ProsodyFilePaths
|
paths: ProsodyFilePaths
|
||||||
@ -97,7 +99,7 @@ interface ProsodyConfig {
|
|||||||
logByDefault: boolean
|
logByDefault: boolean
|
||||||
logExpiration: ConfigLogExpiration
|
logExpiration: ConfigLogExpiration
|
||||||
valuesToHideInDiagnostic: Map<string, string>
|
valuesToHideInDiagnostic: Map<string, string>
|
||||||
needCerticates: boolean
|
certificates: ProsodyConfigCertificates
|
||||||
}
|
}
|
||||||
async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<ProsodyConfig> {
|
async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<ProsodyConfig> {
|
||||||
const logger = options.peertubeHelpers.logger
|
const logger = options.peertubeHelpers.logger
|
||||||
@ -134,7 +136,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
|||||||
const prosodyDomain = await getProsodyDomain(options)
|
const prosodyDomain = await getProsodyDomain(options)
|
||||||
const paths = await getProsodyFilePaths(options)
|
const paths = await getProsodyFilePaths(options)
|
||||||
const roomType = settings['prosody-room-type'] === 'channel' ? 'channel' : 'video'
|
const roomType = settings['prosody-room-type'] === 'channel' ? 'channel' : 'video'
|
||||||
let needCerticates: boolean = false
|
let certificates: ProsodyConfigCertificates = false
|
||||||
|
|
||||||
const apikey = await getAPIKey(options)
|
const apikey = await getAPIKey(options)
|
||||||
valuesToHideInDiagnostic.set('APIKey', apikey)
|
valuesToHideInDiagnostic.set('APIKey', apikey)
|
||||||
@ -182,7 +184,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (enableRoomS2S) {
|
if (enableRoomS2S) {
|
||||||
needCerticates = true
|
certificates = 'generate-self-signed'
|
||||||
const s2sPort = (settings['prosody-s2s-port'] as string) || '5269'
|
const s2sPort = (settings['prosody-s2s-port'] as string) || '5269'
|
||||||
if (!/^\d+$/.test(s2sPort)) {
|
if (!/^\d+$/.test(s2sPort)) {
|
||||||
throw new Error('Invalid s2s port')
|
throw new Error('Invalid s2s port')
|
||||||
@ -238,7 +240,7 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
|||||||
logByDefault,
|
logByDefault,
|
||||||
logExpiration,
|
logExpiration,
|
||||||
valuesToHideInDiagnostic,
|
valuesToHideInDiagnostic,
|
||||||
needCerticates
|
certificates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
import { getProsodyConfig, getProsodyFilePaths, writeProsodyConfig, ProsodyConfig } from './config'
|
import { getProsodyConfig, getProsodyFilePaths, writeProsodyConfig } from './config'
|
||||||
import { startProsodyLogRotate, stopProsodyLogRotate } from './logrotate'
|
import { startProsodyLogRotate, stopProsodyLogRotate } from './logrotate'
|
||||||
|
import {
|
||||||
|
ensureProsodyCertificates, startProsodyCertificatesRenewCheck, stopProsodyCertificatesRenewCheck
|
||||||
|
} from './certificates'
|
||||||
import { disableProxyRoute, enableProxyRoute } from '../routers/webchat'
|
import { disableProxyRoute, enableProxyRoute } from '../routers/webchat'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as child_process from 'child_process'
|
import * as child_process from 'child_process'
|
||||||
@ -347,7 +350,8 @@ async function ensureProsodyRunning (options: RegisterServerOptions): Promise<vo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.info('Prosody is running')
|
logger.info('Prosody is running')
|
||||||
await startProsodyLogRotate(options, filePaths, reloadProsody)
|
await startProsodyLogRotate(options, filePaths)
|
||||||
|
await startProsodyCertificatesRenewCheck(options, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise<void> {
|
async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise<void> {
|
||||||
@ -356,6 +360,7 @@ async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise
|
|||||||
logger.info('Checking if Prosody is running, and shutting it down if so')
|
logger.info('Checking if Prosody is running, and shutting it down if so')
|
||||||
|
|
||||||
stopProsodyLogRotate(options)
|
stopProsodyLogRotate(options)
|
||||||
|
stopProsodyCertificatesRenewCheck(options)
|
||||||
|
|
||||||
// NB: this function is called on plugin unregister, even if prosody is not used
|
// NB: this function is called on plugin unregister, even if prosody is not used
|
||||||
// so we must avoid creating the working dir now
|
// so we must avoid creating the working dir now
|
||||||
@ -373,28 +378,6 @@ async function ensureProsodyNotRunning (options: RegisterServerOptions): Promise
|
|||||||
logger.info(`ProsodyCtl command returned: ${status.message}`)
|
logger.info(`ProsodyCtl command returned: ${status.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureProsodyCertificates (options: RegisterServerOptions, config: ProsodyConfig): Promise<void> {
|
|
||||||
if (!config.needCerticates) { return }
|
|
||||||
options.peertubeHelpers.logger.info('Prosody needs certicicates, checking if certificates are okay...')
|
|
||||||
|
|
||||||
// FIXME: don't generate certicicated everytime, just if it is missing or expired.
|
|
||||||
|
|
||||||
const prosodyDomain = config.host
|
|
||||||
// Using: prososyctl --config /.../prosody.cfg.lua cert generate prosodyDomain.tld
|
|
||||||
await prosodyCtl(options, 'cert', {
|
|
||||||
additionalArgs: ['generate', prosodyDomain],
|
|
||||||
yesMode: true,
|
|
||||||
stdErrFilter: (data) => {
|
|
||||||
// For an unknow reason, `prosodyctl cert generate` outputs openssl data on stderr...
|
|
||||||
// So we filter these logs.
|
|
||||||
if (data.match(/Generating \w+ private key/)) { return false }
|
|
||||||
if (data.match(/^[.+o*\n]*$/m)) { return false }
|
|
||||||
if (data.match(/e is \d+/)) { return false }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getProsodyAbout,
|
getProsodyAbout,
|
||||||
checkProsody,
|
checkProsody,
|
||||||
@ -402,5 +385,7 @@ export {
|
|||||||
testProsodyCorrectlyRunning,
|
testProsodyCorrectlyRunning,
|
||||||
prepareProsody,
|
prepareProsody,
|
||||||
ensureProsodyRunning,
|
ensureProsodyRunning,
|
||||||
ensureProsodyNotRunning
|
ensureProsodyNotRunning,
|
||||||
|
prosodyCtl,
|
||||||
|
reloadProsody
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
import type { ProsodyFilePaths } from './config/paths'
|
import type { ProsodyFilePaths } from './config/paths'
|
||||||
import { isDebugMode } from '../debug'
|
import { isDebugMode } from '../debug'
|
||||||
|
import { reloadProsody } from './ctl'
|
||||||
|
|
||||||
type Rotate = (file: string, options: {
|
type Rotate = (file: string, options: {
|
||||||
count?: number
|
count?: number
|
||||||
@ -12,7 +13,6 @@ interface ProsodyLogRotate {
|
|||||||
timer: NodeJS.Timeout
|
timer: NodeJS.Timeout
|
||||||
lastRotation: number
|
lastRotation: number
|
||||||
}
|
}
|
||||||
type ReloadProsody = (options: RegisterServerOptions) => Promise<boolean>
|
|
||||||
|
|
||||||
let logRotate: ProsodyLogRotate | undefined
|
let logRotate: ProsodyLogRotate | undefined
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ async function _rotate (options: RegisterServerOptions, path: string): Promise<v
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
function startProsodyLogRotate (options: RegisterServerOptions, paths: ProsodyFilePaths, reload: ReloadProsody): void {
|
function startProsodyLogRotate (options: RegisterServerOptions, paths: ProsodyFilePaths): void {
|
||||||
const logger = options.peertubeHelpers.logger
|
const logger = options.peertubeHelpers.logger
|
||||||
const debugMode = isDebugMode(options)
|
const debugMode = isDebugMode(options)
|
||||||
const checkInterval = debugMode ? 60 * 1000 : 60 * 60 * 1000 // check every hour
|
const checkInterval = debugMode ? 60 * 1000 : 60 * 60 * 1000 // check every hour
|
||||||
@ -63,7 +63,7 @@ function startProsodyLogRotate (options: RegisterServerOptions, paths: ProsodyFi
|
|||||||
_rotate(options, paths.error)
|
_rotate(options, paths.error)
|
||||||
])
|
])
|
||||||
p.then(() => {
|
p.then(() => {
|
||||||
reload(options).then(() => {
|
reloadProsody(options).then(() => {
|
||||||
logger.debug('Prosody reloaded')
|
logger.debug('Prosody reloaded')
|
||||||
}, () => {
|
}, () => {
|
||||||
logger.error('Prosody failed to reload')
|
logger.error('Prosody failed to reload')
|
||||||
|
Loading…
Reference in New Issue
Block a user