[CalendarServer-changes] [13925] CalendarServer/trunk/calendarserver/tap
source_changes at macosforge.org
source_changes at macosforge.org
Fri Aug 29 11:44:34 PDT 2014
Revision: 13925
http://trac.calendarserver.org//changeset/13925
Author: sagen at apple.com
Date: 2014-08-29 11:44:34 -0700 (Fri, 29 Aug 2014)
Log Message:
-----------
If pre-flight checks don't pass, shut down more gracefully
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tap/caldav.py
CalendarServer/trunk/calendarserver/tap/test/test_util.py
CalendarServer/trunk/calendarserver/tap/util.py
Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py 2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py 2014-08-29 18:44:34 UTC (rev 13925)
@@ -127,7 +127,7 @@
checkDirectories, getRootResource,
oracleConnectorFromConfig, pgConnectorFromConfig,
pgServiceFromConfig, getDBPool, MemoryLimitService,
- storeFromConfig, getSSLPassphrase, PreFlightChecksStep
+ storeFromConfig, getSSLPassphrase, preFlightChecks
)
try:
from calendarserver.version import version
@@ -1406,8 +1406,7 @@
uid, gid = getSystemIDs(config.UserName, config.GroupName)
return self.storageService(
- toolServiceCreator, None, uid=uid, gid=gid, directory=None,
- preFlightChecks=False
+ toolServiceCreator, None, uid=uid, gid=gid, directory=None
)
@@ -1445,7 +1444,7 @@
uid, gid = getSystemIDs(config.UserName, config.GroupName)
svc = self.storageService(
- agentServiceCreator, None, uid=uid, gid=gid, preFlightChecks=False
+ agentServiceCreator, None, uid=uid, gid=gid
)
agentLoggingService = ErrorLoggingMultiService(
config.ErrorLogEnabled,
@@ -1458,8 +1457,7 @@
def storageService(
- self, createMainService, logObserver, uid=None, gid=None, directory=None,
- preFlightChecks=True
+ self, createMainService, logObserver, uid=None, gid=None, directory=None
):
"""
If necessary, create a service to be started used for storage; for
@@ -1586,11 +1584,6 @@
)
)
- if preFlightChecks:
- pps.addStep(
- PreFlightChecksStep(config)
- )
-
pps.addStep(
UpgradeReleaseLockStep(store)
)
@@ -1650,6 +1643,7 @@
Create a master service to coordinate a multi-process configuration,
spawning subprocesses that use L{makeService_Slave} to perform work.
"""
+
s = ErrorLoggingMultiService(
config.ErrorLogEnabled,
config.ErrorLogFile,
@@ -1657,6 +1651,18 @@
config.ErrorLogMaxRotatedFiles
)
+ # Perform early pre-flight checks. If this returns True, continue on.
+ # Otherwise one of two things will happen:
+ # A) preFlightChecks( ) will have registered an "after startup" system
+ # event trigger to request a shutdown via the
+ # ServiceDisablingProgram, and we want to return our
+ # ErrorLoggingMultiService so that logging is set up enough to
+ # emit our reason for shutting down into the error.log.
+ # B) preFlightChecks( ) will sys.exit(1) if there is not a
+ # ServiceDisablingProgram configured.
+ if not preFlightChecks(config):
+ return s
+
# Add a service to re-exec the master when it receives SIGHUP
ReExecService(config.PIDFile).setServiceParent(s)
Modified: CalendarServer/trunk/calendarserver/tap/test/test_util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_util.py 2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/test/test_util.py 2014-08-29 18:44:34 UTC (rev 13925)
@@ -14,16 +14,15 @@
# limitations under the License.
##
-import OpenSSL
from calendarserver.tap.util import (
- MemoryLimitService, Stepper, PreFlightChecksStep
+ MemoryLimitService, Stepper, verifyTLSCertificate
)
from twistedcaldav.util import computeProcessCount
from twistedcaldav.test.util import TestCase
from twisted.internet.task import Clock
from twisted.internet.defer import succeed, inlineCallbacks
from twisted.python.filepath import FilePath
-from twistedcaldav.config import ConfigDict, ConfigurationError
+from twistedcaldav.config import ConfigDict
class ProcessCountTestCase(TestCase):
@@ -236,54 +235,41 @@
['one success', 'two failure', 'three success', 'four failure'])
-class PreFlightChecksStepTestCase(TestCase):
+class PreFlightChecksTestCase(TestCase):
"""
Verify that missing, empty, or bogus TLS Certificates are detected
"""
- @inlineCallbacks
def test_missingCertificate(self):
- step = PreFlightChecksStep(
+ success, reason = verifyTLSCertificate(
ConfigDict(
{
"SSLCertificate": "missing",
}
)
)
- try:
- yield step.stepWithResult(None)
- except ConfigurationError as e:
- self.assertTrue("Missing" in str(e))
- else:
- self.fail("Did not raise ConfigurationError")
+ self.assertFalse(success)
- @inlineCallbacks
def test_emptyCertificate(self):
certFilePath = FilePath(self.mktemp())
certFilePath.setContent("")
- step = PreFlightChecksStep(
+ success, reason = verifyTLSCertificate(
ConfigDict(
{
"SSLCertificate": certFilePath.path,
}
)
)
- try:
- yield step.stepWithResult(None)
- except ConfigurationError as e:
- self.assertTrue("Empty" in str(e))
- else:
- self.fail("Did not raise ConfigurationError")
+ self.assertFalse(success)
- @inlineCallbacks
def test_bogusCertificate(self):
certFilePath = FilePath(self.mktemp())
certFilePath.setContent("bogus")
keyFilePath = FilePath(self.mktemp())
keyFilePath.setContent("bogus")
- step = PreFlightChecksStep(
+ success, reason = verifyTLSCertificate(
ConfigDict(
{
"SSLCertificate": certFilePath.path,
@@ -294,9 +280,4 @@
}
)
)
- try:
- yield step.stepWithResult(None)
- except OpenSSL.SSL.Error:
- pass
- else:
- self.fail("Did not raise OpenSSL.SSL.Error")
+ self.assertFalse(success)
Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py 2014-08-28 22:35:37 UTC (rev 13924)
+++ CalendarServer/trunk/calendarserver/tap/util.py 2014-08-29 18:44:34 UTC (rev 13925)
@@ -20,12 +20,12 @@
"""
__all__ = [
+ "FakeRequest",
+ "getDBPool",
"getRootResource",
- "getDBPool",
- "FakeRequest",
+ "getSSLPassphrase",
"MemoryLimitService",
- "PreFlightChecksStep",
- "getSSLPassphrase",
+ "preFlightChecks",
]
import errno
@@ -34,6 +34,7 @@
import psutil
from socket import fromfd, AF_UNIX, SOCK_STREAM, socketpair
from subprocess import Popen, PIPE
+import sys
from twext.internet.ssl import ChainingOpenSSLContextFactory
@@ -1114,63 +1115,108 @@
return self.deferred
-class PreFlightChecksStep(object):
+def requestShutdown(programPath, reason):
"""
- A place to make any other checks before finishing up the
- PreProcessingService.
- """
+ Log the shutdown reason and call the shutdown-requesting program.
- def __init__(self, config):
- self.config = config
+ In the case the service is spawned by launchd (or equivalent), if our
+ service decides it needs to shut itself down, because of a misconfiguration,
+ for example, we can't just exit. We may need to go through the system
+ machinery to unload our job, manage reverse proxies, update admin UI, etc.
+ Therefore you can configure the ServiceDisablingProgram plist key to point
+ to a program to run which will stop our service.
+ @param programPath: the full path to a program to call (with no args)
+ @type programPath: C{str}
+ @param reason: a shutdown reason to log
+ @type reason: C{str}
+ """
+ log.error("Shutting down Calendar and Contacts server")
+ log.error(reason)
+ Popen(
+ args=[config.ServiceDisablingProgram],
+ stdout=PIPE,
+ stderr=PIPE,
+ ).communicate()
- def stepWithResult(self, result):
- self.verifyTLSCertificate()
- return succeed(None)
+def preFlightChecks(config):
+ """
+ Perform checks prior to spawning any processes. Returns True if the checks
+ are ok, False if they don't and we have a ServiceDisablingProgram configured.
+ Otherwise exits.
+ """
- def verifyTLSCertificate(self):
- """
- If a TLS certificate is configured, make sure it exists, is non empty,
- and that it's valid.
- """
+ success, reason = verifyTLSCertificate(config)
- if self.config.SSLCertificate:
- if not os.path.exists(self.config.SSLCertificate):
- log.error(
- "The configured TLS certificate ({cert}) is missing",
- cert=self.config.SSLCertificate
+ if not success:
+ if config.ServiceDisablingProgram:
+ # If pre-flight checks fail, we don't want launchd to
+ # repeatedly launch us, we want our job to get unloaded.
+ # If the config.ServiceDisablingProgram is assigned and exists
+ # we schedule it to run after startService finishes.
+ # Its job is to carry out the platform-specific tasks of disabling
+ # the service.
+ if os.path.exists(config.ServiceDisablingProgram):
+ addSystemEventTrigger(
+ "after", "startup",
+ requestShutdown, config.ServiceDisablingProgram, reason
)
- raise ConfigurationError("Missing certificate file")
+ return False
+
else:
- return
+ sys.exit(1)
- length = os.stat(self.config.SSLCertificate).st_size
- if length == 0:
- log.error(
- "The configured TLS certificate ({cert}) is empty",
- cert=self.config.SSLCertificate
+ return True
+
+
+def verifyTLSCertificate(config):
+ """
+ If a TLS certificate is configured, make sure it exists, is non empty,
+ and that it's valid.
+ """
+
+ if config.SSLCertificate:
+ if not os.path.exists(config.SSLCertificate):
+ message = (
+ "The configured TLS certificate ({cert}) is missing".format(
+ cert=config.SSLCertificate
)
- raise ConfigurationError("Empty certificate file")
+ )
+ return False, message
+ else:
+ return True, "TLS disabled"
- try:
- ChainingOpenSSLContextFactory(
- self.config.SSLPrivateKey,
- self.config.SSLCertificate,
- certificateChainFile=self.config.SSLAuthorityChain,
- passwdCallback=getSSLPassphrase,
- sslmethod=getattr(OpenSSL.SSL, self.config.SSLMethod),
- ciphers=self.config.SSLCiphers.strip()
+ length = os.stat(config.SSLCertificate).st_size
+ if length == 0:
+ message = (
+ "The configured TLS certificate ({cert}) is empty".format(
+ cert=config.SSLCertificate
+ )
)
- except Exception as e:
- log.error(
- "The configured TLS certificate ({cert}) cannot be used: {reason}",
- cert=self.config.SSLCertificate,
+ return False, message
+
+ try:
+ ChainingOpenSSLContextFactory(
+ config.SSLPrivateKey,
+ config.SSLCertificate,
+ certificateChainFile=config.SSLAuthorityChain,
+ passwdCallback=getSSLPassphrase,
+ sslmethod=getattr(OpenSSL.SSL, config.SSLMethod),
+ ciphers=config.SSLCiphers.strip()
+ )
+ except Exception as e:
+ message = (
+ "The configured TLS certificate ({cert}) cannot be used: {reason}".format(
+ cert=config.SSLCertificate,
reason=str(e)
)
- raise
+ )
+ return False, message
+ return True, "TLS enabled"
+
def getSSLPassphrase(*ignored):
if not config.SSLPrivateKey:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140829/c0f38732/attachment-0001.html>
More information about the calendarserver-changes
mailing list