From 8a2abf09b28d8eeb28ab8c6a45c5d0ee7f2864f2 Mon Sep 17 00:00:00 2001 From: Matej Sekoranja Date: Tue, 9 Sep 2014 14:28:43 +0200 Subject: [PATCH] access security --- pvAccessCPP.files | 2 + src/pva/pvaConstants.h | 2 +- src/remote/Makefile | 2 + src/remote/blockingTCPConnector.cpp | 2 - src/remote/blockingUDP.h | 12 + src/remote/codec.cpp | 210 +++++++++++- src/remote/codec.h | 36 +- src/remote/remote.h | 30 +- src/remote/security.cpp | 13 + src/remote/security.h | 458 +++++++++++++++++++++++++ src/remote/serializationHelper.cpp | 40 ++- src/remote/serializationHelper.h | 19 +- src/remoteClient/clientContextImpl.cpp | 49 +-- src/server/baseChannelRequester.cpp | 14 +- src/server/responseHandlers.cpp | 30 +- src/server/serverContext.cpp | 6 + src/server/serverContext.h | 1 + testApp/remote/testCodec.cpp | 10 + 18 files changed, 863 insertions(+), 73 deletions(-) create mode 100644 src/remote/security.cpp create mode 100644 src/remote/security.h diff --git a/pvAccessCPP.files b/pvAccessCPP.files index 18d359a..04ecb7c 100644 --- a/pvAccessCPP.files +++ b/pvAccessCPP.files @@ -235,3 +235,5 @@ testApp/utils/testAtomicBoolean.cpp testApp/utils/testHexDump.cpp testApp/utils/testInetAddressUtils.cpp testApp/utils/transportRegistryTest.cpp +src/remote/security.h +src/remote/security.cpp diff --git a/src/pva/pvaConstants.h b/src/pva/pvaConstants.h index 421f5dd..2d8d97d 100644 --- a/src/pva/pvaConstants.h +++ b/src/pva/pvaConstants.h @@ -41,7 +41,7 @@ namespace pvAccess { const epics::pvData::int16 PVA_MESSAGE_HEADER_SIZE = 8; /** All messages must be aligned to 8-bytes (64-bit). */ - const epics::pvData::int32 PVA_ALIGNMENT = 1; + const epics::pvData::int32 PVA_ALIGNMENT = 1; //8; /** * UDP maximum send message size. diff --git a/src/remote/Makefile b/src/remote/Makefile index a0479b6..0218986 100644 --- a/src/remote/Makefile +++ b/src/remote/Makefile @@ -3,6 +3,7 @@ SRC_DIRS += $(PVACCESS_SRC)/remote INC += remote.h +INC += security.h INC += blockingUDP.h INC += beaconHandler.h INC += blockingTCP.h @@ -22,3 +23,4 @@ LIBSRCS += blockingTCPAcceptor.cpp LIBSRCS += transportRegistry.cpp LIBSRCS += serializationHelper.cpp LIBSRCS += codec.cpp +LIBSRCS += security.cpp diff --git a/src/remote/blockingTCPConnector.cpp b/src/remote/blockingTCPConnector.cpp index 047f546..87711f1 100644 --- a/src/remote/blockingTCPConnector.cpp +++ b/src/remote/blockingTCPConnector.cpp @@ -166,8 +166,6 @@ namespace epics { THROW_BASE_EXCEPTION(temp.str().c_str()); } - // TODO send security token - LOG(logLevelDebug, "Connected to PVA server: %s.", ipAddrStr); _namedLocker.releaseSynchronizationObject(&address); diff --git a/src/remote/blockingUDP.h b/src/remote/blockingUDP.h index 33119a0..910e5c5 100644 --- a/src/remote/blockingUDP.h +++ b/src/remote/blockingUDP.h @@ -121,6 +121,18 @@ namespace epics { // noop } + virtual void authNZInitialize(void*) { + // noop + } + + virtual void authNZMessage(epics::pvData::PVField::shared_pointer const & data) { + // noop + } + + virtual std::tr1::shared_ptr getSecuritySession() const { + return std::tr1::shared_ptr(); + } + // NOTE: this is not yet used for UDP virtual void setByteOrder(int byteOrder) { // called from receive thread... or before processing diff --git a/src/remote/codec.cpp b/src/remote/codec.cpp index 02549fd..94c2775 100644 --- a/src/remote/codec.cpp +++ b/src/remote/codec.cpp @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #define epicsExportSharedSymbols #include @@ -23,7 +26,9 @@ #include #include #include +#include +using namespace std; using namespace epics::pvData; using namespace epics::pvAccess; @@ -1433,6 +1438,11 @@ namespace epics { void BlockingTCPTransportCodec::internalClose(bool force) { BlockingSocketAbstractCodec::internalClose(force); + + // TODO sync + if (_securitySession) + _securitySession->close(); + if (IS_LOGGABLE(logLevelDebug)) { LOG(logLevelDebug, @@ -1461,9 +1471,47 @@ namespace epics { _verifiedEvent.signal(); } + void BlockingTCPTransportCodec::authNZMessage(epics::pvData::PVField::shared_pointer const & data) { + // TODO sync + if (_securitySession) + _securitySession->messageReceived(data); + else + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelWarn, "authNZ message received from '%s' but no security plug-in session active.", ipAddrStr); + } + } + class SecurityPluginMessageTransportSender : public TransportSender { + public: + POINTER_DEFINITIONS(SecurityPluginMessageTransportSender); + SecurityPluginMessageTransportSender(PVField::shared_pointer const & data) : + m_data(data) + { + } + + void send(ByteBuffer* buffer, TransportSendControl* control) { + control->startMessage((int8)5, 0); + SerializationHelper::serializeFull(buffer, control, m_data); + // send immediately + control->flush(true); + } + + void lock() {} + void unlock() {} + + private: + PVField::shared_pointer m_data; + }; + + void BlockingTCPTransportCodec::sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) { + // TODO not optimal since it allocates a new object every time + SecurityPluginMessageTransportSender::shared_pointer spmts(new SecurityPluginMessageTransportSender(data)); + enqueueSendRequest(spmts); + } @@ -1477,7 +1525,7 @@ namespace epics { int32_t receiveBufferSize) : BlockingTCPTransportCodec(true, context, channel, responseHandler, sendBufferSize, receiveBufferSize, PVA_DEFAULT_PRIORITY), - _lastChannelSID(0), _verifyOrVerified(false) + _lastChannelSID(0), _verifyOrVerified(false), _securityRequired(false) { // NOTE: priority not yet known, default priority is used to @@ -1574,8 +1622,29 @@ namespace epics { buffer->putShort(0x7FFF); // list of authNZ plugin names - // TODO - buffer->putByte(0); + map securityPlugins; + vector validSPNames; + validSPNames.reserve(securityPlugins.size()); + + for (map::const_iterator iter = + securityPlugins.begin(); + iter != securityPlugins.end(); iter++) + { + SecurityPlugin::shared_pointer securityPlugin = iter->second; + if (securityPlugin->isValidFor(_socketAddress)) + validSPNames.push_back(securityPlugin->getId()); + } + + size_t validSPCount = validSPNames.size(); + + SerializeHelper::writeSize(validSPCount, buffer, this); + for (vector::const_iterator iter = + validSPNames.begin(); + iter != validSPNames.end(); iter++) + SerializeHelper::serializeString(*iter, buffer, this); + + // TODO sync + _securityRequired = (validSPCount > 0); // send immediately control->flush(true); @@ -1625,7 +1694,82 @@ namespace epics { destroyAllChannels(); } + void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status) + { + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelDebug, "Authentication completed with status '%s' for PVA client: %s.", Status::StatusTypeName[status.getType()], ipAddrStr); + } + if (!isVerified()) // TODO sync + verified(status); + else if (!status.isSuccess()) + { + string errorMessage = "Re-authentication failed: " + status.getMessage(); + if (!status.getStackDump().empty()) + errorMessage += "\n" + status.getStackDump(); + LOG(logLevelInfo, errorMessage.c_str()); + + close(); + } + } + + epics::pvData::Status BlockingServerTCPTransportCodec::invalidSecurityPluginNameStatus(Status::STATUSTYPE_ERROR, "invalid security plug-in name"); + + void BlockingServerTCPTransportCodec::authNZInitialize(void *arg) + { + struct InitData { + std::string securityPluginName; + PVField::shared_pointer data; + }; + + InitData* initData = static_cast(arg); + + // check if plug-in name is valid + SecurityPlugin::shared_pointer securityPlugin; + + map::iterator spIter = + _context->getSecurityPlugins().find(initData->securityPluginName); + if (spIter != _context->getSecurityPlugins().end()) + securityPlugin = spIter->second; + if (!securityPlugin) + { + if (_securityRequired) + { + verified(invalidSecurityPluginNameStatus); + return; + } + else + { + securityPlugin = NoSecurityPlugin::INSTANCE; + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelDebug, "No security plug-in installed, selecting default plug-in '%s' for PVA client: %s.", securityPlugin->getId().c_str(), ipAddrStr); + } + } + } + + if (!securityPlugin->isValidFor(_socketAddress)) + verified(invalidSecurityPluginNameStatus); + + if (IS_LOGGABLE(logLevelDebug)) + { + char ipAddrStr[48]; + ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr)); + LOG(logLevelDebug, "Accepted security plug-in '%s' for PVA client: %s.", initData->securityPluginName.c_str(), ipAddrStr); + } + + // create session + SecurityPluginControl::shared_pointer spc = std::tr1::dynamic_pointer_cast(shared_from_this()); + + // TODO sync + _securitySession = securityPlugin->createSession(_socketAddress, spc, initData->data); + } @@ -1858,9 +2002,23 @@ namespace epics { // QoS (aka connection priority) buffer->putShort(getPriority()); - // authNZ plugin name - // TODO - SerializeHelper::serializeString("", buffer, control); + // TODO sync + if (_securitySession) + { + // selected authNZ plug-in name + SerializeHelper::serializeString(_securitySession->getSecurityPlugin()->getId(), buffer, control); + + // optional authNZ plug-in initialization data + SerializationHelper::serializeFull(buffer, control, _securitySession->initializationData()); + } + else + { + // emptry authNZ plug-in name + SerializeHelper::serializeString("", buffer, control); + + // no authNZ plug-in initialization data + SerializationHelper::serializeNullField(buffer, control); + } // send immediately control->flush(true); @@ -1872,8 +2030,44 @@ namespace epics { } } - - + + + void BlockingClientTCPTransportCodec::authNZInitialize(void *arg) + { + vector* offeredSecurityPlugins = static_cast< vector* >(arg); + if (!offeredSecurityPlugins->empty()) + { + map& availableSecurityPlugins = + _context->getSecurityPlugins(); + + for (vector::const_iterator offeredSP = offeredSecurityPlugins->begin(); + offeredSP != offeredSecurityPlugins->end(); offeredSP++) + { + map::iterator spi = availableSecurityPlugins.find(*offeredSP); + if (spi != availableSecurityPlugins.end()) + { + SecurityPlugin::shared_pointer securityPlugin = spi->second; + if (securityPlugin->isValidFor(_socketAddress)) + { + // create session + SecurityPluginControl::shared_pointer spc = std::tr1::dynamic_pointer_cast(shared_from_this()); + + // TODO sync + _securitySession = securityPlugin->createSession(_socketAddress, spc, PVField::shared_pointer()); + } + } + } + } + + TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast(shared_from_this()); + enqueueSendRequest(transportSender); + } + + void BlockingClientTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status) + { + // noop for client side (server will send ConnectionValidation message) + } + } } } diff --git a/src/remote/codec.h b/src/remote/codec.h index ad48350..3c36f54 100644 --- a/src/remote/codec.h +++ b/src/remote/codec.h @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -415,7 +416,8 @@ namespace epics { class BlockingTCPTransportCodec : - public BlockingSocketAbstractCodec + public BlockingSocketAbstractCodec, + public SecurityPluginControl { @@ -522,6 +524,17 @@ namespace epics { void verified(epics::pvData::Status const & status); + bool isVerified() const { return _verified; } // TODO sync + + std::tr1::shared_ptr getSecuritySession() const { + // TODO sync + return _securitySession; + } + + void authNZMessage(epics::pvData::PVField::shared_pointer const & data); + + void sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data); + protected: BlockingTCPTransportCodec( @@ -548,6 +561,8 @@ namespace epics { IntrospectionRegistry _incomingIR; IntrospectionRegistry _outgoingIR; + SecuritySession::shared_pointer _securitySession; + private: std::auto_ptr _responseHandler; @@ -558,6 +573,7 @@ namespace epics { bool _verified; epics::pvData::Mutex _verifiedMutex; epics::pvData::Event _verifiedEvent; + }; @@ -619,10 +635,6 @@ namespace epics { int getChannelCount(); - epics::pvData::PVField::shared_pointer getSecurityToken() { - return epics::pvData::PVField::shared_pointer(); - } - void lock() { // noop } @@ -655,6 +667,10 @@ namespace epics { // noop on server-side } + void authNZInitialize(void *); + + void authenticationCompleted(epics::pvData::Status const & status); + void send(epics::pvData::ByteBuffer* buffer, TransportSendControl* control); @@ -684,6 +700,10 @@ namespace epics { bool _verifyOrVerified; + bool _securityRequired; + + static epics::pvData::Status invalidSecurityPluginNameStatus; + }; class epicsShareClass BlockingClientTCPTransportCodec : @@ -759,7 +779,11 @@ namespace epics { void send(epics::pvData::ByteBuffer* buffer, TransportSendControl* control); - + + void authNZInitialize(void *); + + void authenticationCompleted(epics::pvData::Status const & status); + protected: virtual void internalClose(bool force); diff --git a/src/remote/remote.h b/src/remote/remote.h index 14f9a30..d024fb5 100644 --- a/src/remote/remote.h +++ b/src/remote/remote.h @@ -12,6 +12,9 @@ # undef epicsExportSharedSymbols #endif +#include +#include + #include #include @@ -157,6 +160,7 @@ namespace epics { }; class TransportClient; + class SecuritySession; /** * Interface defining transport (connection). @@ -288,9 +292,24 @@ namespace epics { * @return true if connected. */ virtual bool isClosed() = 0; + + /** + * Used to initialize authNZ (select security plug-in). + * @param data + */ + virtual void authNZInitialize(void*) = 0; + + /** + * Pass data to the active security plug-in session. + * @param data the data (any data), can be null. + */ + virtual void authNZMessage(epics::pvData::PVField::shared_pointer const & data) = 0; + + virtual std::tr1::shared_ptr getSecuritySession() const = 0; }; class Channel; + class SecurityPlugin; /** * Not public IF, used by Transports, etc. @@ -311,6 +330,11 @@ namespace epics { virtual Configuration::shared_pointer getConfiguration() = 0; + /** + * Get map of available security plug-ins. + * @return the map of available security plug-ins + */ + virtual std::map >& getSecurityPlugins() = 0; /// @@ -465,12 +489,6 @@ namespace epics { virtual ~ChannelHostingTransport() {} - /** - * Get security token. - * @return security token, can be null. - */ - virtual epics::pvData::PVField::shared_pointer getSecurityToken() = 0; - /** * Preallocate new channel SID. * @return new channel server id (SID). diff --git a/src/remote/security.cpp b/src/remote/security.cpp new file mode 100644 index 0000000..933a97a --- /dev/null +++ b/src/remote/security.cpp @@ -0,0 +1,13 @@ +/** +* Copyright - See the COPYRIGHT that is included with this distribution. +* pvAccessCPP is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +*/ + +#define epicsExportSharedSymbols +#include + +using namespace epics::pvAccess; + +NoSecurityPlugin::shared_pointer NoSecurityPlugin::INSTANCE(new NoSecurityPlugin()); + diff --git a/src/remote/security.h b/src/remote/security.h new file mode 100644 index 0000000..5cf3b74 --- /dev/null +++ b/src/remote/security.h @@ -0,0 +1,458 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvAccessCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#ifndef SECURITY_H +#define SECURITY_H + +#ifdef epicsExportSharedSymbols +# define securityEpicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include +#include +#include + +#include +#include +#include + +#ifdef securityEpicsExportSharedSymbols +# define epicsExportSharedSymbols +# undef securityEpicsExportSharedSymbols +#endif + +#include +#include + +#include + +namespace epics { + namespace pvAccess { + + // notify client only on demand, configurable via pvRequest + // add the following method to ChannelRequest: + // void credentialsChanged(std::vector credentials); + + + // pvAccess message: channel client id, ioid (if invalid then it's for channel) and array of bitSets + // or leave to the plugin? + + + // when clients gets initial credentialsChanged call before create is called + // and then on each change + + class ChannelSecuritySession { + public: + POINTER_DEFINITIONS(ChannelSecuritySession); + + virtual ~ChannelSecuritySession() {} + + /// closes this session + virtual void close() = 0; + + // for every authroizeCreate... a release() must be called + virtual void release(pvAccessID ioid) = 0; + + // bitSet w/ one bit + virtual epics::pvData::Status authorizeCreateChannelProcess( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + virtual epics::pvData::Status authorizeProcess(pvAccessID ioid) = 0; + + // bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateChannelGet( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + virtual epics::pvData::Status authorizeGet(pvAccessID ioid) = 0; + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateChannelPut( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + virtual epics::pvData::Status authorizePut( + pvAccessID ioid, + epics::pvData::PVStructure::shared_pointer const & dataToPut, + epics::pvData::BitSet::shared_pointer const & fieldsToPut) = 0; + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // process: bitSet w/ one bit (allowed, not allowed) + virtual epics::pvData::Status authorizeCreateChannelPutGet( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + virtual epics::pvData::Status authorizePutGet( + pvAccessID ioid, + epics::pvData::PVStructure::shared_pointer const & dataToPut, + epics::pvData::BitSet::shared_pointer const & fieldsToPut) = 0; + + // bitSet w/ one bit + virtual epics::pvData::Status authorizeCreateChannelRPC( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + // one could authorize per operation basis + virtual epics::pvData::Status authorizeRPC( + pvAccessID ioid, epics::pvData::PVStructure::shared_pointer const & arguments) = 0; + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateMonitor( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + virtual epics::pvData::Status authorizeMonitor(pvAccessID ioid) = 0; + + // read: bitSet w/ one bit (allowed, not allowed) and rest put/get/set length + virtual epics::pvData::Status authorizeCreateChannelArray( + pvAccessID ioid, epics::pvData::PVStructure const & pvRequest) = 0; + // use authorizeGet + virtual epics::pvData::Status authorizePut(pvAccessID ioid, epics::pvData::PVArray::shared_pointer const & dataToPut) = 0; + virtual epics::pvData::Status authorizeSetLength(pvAccessID ioid) = 0; + + + // introspection authorization + virtual epics::pvData::Status authorizeGetField(pvAccessID ioid, std::string const & subField) = 0; + }; + + class SecurityPlugin; + + class epicsShareClass SecurityException: public std::runtime_error { + public: + explicit SecurityException(std::string const & what): std::runtime_error(what) {} + }; + + class epicsShareClass SecuritySession { + public: + POINTER_DEFINITIONS(SecuritySession); + + // optional (can be null) initialization data for the remote party + // client to server + virtual epics::pvData::PVField::shared_pointer initializationData() = 0; + + // get parent + virtual std::tr1::shared_ptr getSecurityPlugin() = 0; + + // can be called any time, for any reason + virtual void messageReceived(epics::pvData::PVField::shared_pointer const & data) = 0; + + /// closes this session + virtual void close() = 0; + + // notification to the client on allowed requests (bitSet, a bit per request) + virtual ChannelSecuritySession::shared_pointer createChannelSession(std::string const & channelName) + throw (SecurityException) = 0; + }; + + class epicsShareClass SecurityPluginControl { + public: + POINTER_DEFINITIONS(SecurityPluginControl); + + virtual ~SecurityPluginControl() {} + + // can be called any time, for any reason + virtual void sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) = 0; + + // if Status.isSuccess() == false, + // pvAccess will send status to the client and close the connection + // can be called more then once (in case of re-authentication process) + virtual void authenticationCompleted(epics::pvData::Status const & status) = 0; + }; + + + class epicsShareClass SecurityPlugin { + public: + POINTER_DEFINITIONS(SecurityPlugin); + + virtual ~SecurityPlugin() {} + + /** + * Short, unique name for the plug-in, used to identify the plugin. + * @return the ID. + */ + virtual std::string getId() const = 0; + + /** + * Description of the security plug-in. + * @return the description string. + */ + virtual std::string getDescription() const = 0; + + /** + * Check whether the remote instance with given network address is + * valid to use this security plug-in to authNZ. + * @param remoteAddress + * @return true if this security plugin can be used for remote instance. + */ + virtual bool isValidFor(osiSockAddr const & remoteAddress) const = 0; + + /** + * Create a security session (usually one per transport). + * @param remoteAddress + * @return a new session. + * @throws SecurityException + */ + // authentication must be done immediately when connection is established (timeout 3seconds), + // later on authentication process can be repeated + // the server and the client can exchange (arbitrary number) of messages using SecurityPluginControl.sendMessage() + // the process completion must be notified by calling AuthenticationControl.completed() + virtual SecuritySession::shared_pointer createSession( + osiSockAddr const & remoteAddress, + SecurityPluginControl::shared_pointer const & control, + epics::pvData::PVField::shared_pointer const & data) throw (SecurityException) = 0; + }; + + + + class epicsShareClass NoSecurityPlugin : + public SecurityPlugin, + public SecuritySession, + public ChannelSecuritySession, + public std::tr1::enable_shared_from_this { + protected: + NoSecurityPlugin() {} + + public: + POINTER_DEFINITIONS(NoSecurityPlugin); + + static NoSecurityPlugin::shared_pointer INSTANCE; + + virtual ~NoSecurityPlugin() {} + + // optional (can be null) initialization data for the remote party + // client to server + virtual epics::pvData::PVField::shared_pointer initializationData() { + return epics::pvData::PVField::shared_pointer(); + } + + // get parent + virtual std::tr1::shared_ptr getSecurityPlugin() { + return std::tr1::dynamic_pointer_cast(shared_from_this()); + } + + // can be called any time, for any reason + virtual void messageReceived(epics::pvData::PVField::shared_pointer const & data) { + // noop + } + + /// closes this session + virtual void close() { + // noop + } + + // notification to the client on allowed requests (bitSet, a bit per request) + virtual ChannelSecuritySession::shared_pointer createChannelSession(std::string const & /*channelName*/) + throw (SecurityException) + { + return shared_from_this(); + } + + /** + * Short, unique name for the plug-in, used to identify the plugin. + * @return the ID. + */ + virtual std::string getId() const { + return "none"; + } + + /** + * Description of the security plug-in. + * @return the description string. + */ + virtual std::string getDescription() const { + return "No security plug-in"; + } + + /** + * Check whether the remote instance with given network address is + * valid to use this security plug-in to authNZ. + * @param remoteAddress + * @return true if this security plugin can be used for remote instance. + */ + virtual bool isValidFor(osiSockAddr const & /*remoteAddress*/) const { + return true; + } + + /** + * Create a security session (usually one per transport). + * @param remoteAddress + * @return a new session. + * @throws SecurityException + */ + // authentication must be done immediately when connection is established (timeout 3seconds), + // later on authentication process can be repeated + // the server and the client can exchange (arbitrary number) of messages using SecurityPluginControl.sendMessage() + // the process completion must be notified by calling AuthenticationControl.completed() + virtual SecuritySession::shared_pointer createSession( + osiSockAddr const & /*remoteAddress*/, + SecurityPluginControl::shared_pointer const & control, + epics::pvData::PVField::shared_pointer const & /*data*/) throw (SecurityException) { + control->authenticationCompleted(epics::pvData::Status::Ok); + return std::tr1::dynamic_pointer_cast(shared_from_this()); + } + + // for every authroizeCreate... a release() must be called + virtual void release(pvAccessID ioid) { + // noop + } + + // bitSet w/ one bit + virtual epics::pvData::Status authorizeCreateChannelProcess( + pvAccessID ioid, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizeProcess(pvAccessID /*ioid*/) { + return epics::pvData::Status::Ok; + } + + // bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateChannelGet( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizeGet(pvAccessID /*ioid*/) { + return epics::pvData::Status::Ok; + } + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateChannelPut( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizePut( + pvAccessID /*ioid*/, + epics::pvData::PVStructure::shared_pointer const & /*dataToPut*/, + epics::pvData::BitSet::shared_pointer const & /*fieldsToPut*/) { + return epics::pvData::Status::Ok; + } + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // write: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + // process: bitSet w/ one bit (allowed, not allowed) + virtual epics::pvData::Status authorizeCreateChannelPutGet( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizePutGet( + pvAccessID /*ioid*/, + epics::pvData::PVStructure::shared_pointer const & /*dataToPut*/, + epics::pvData::BitSet::shared_pointer const & /*fieldsToPut*/) { + return epics::pvData::Status::Ok; + } + + // bitSet w/ one bit + virtual epics::pvData::Status authorizeCreateChannelRPC( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + // one could authorize per operation basis + virtual epics::pvData::Status authorizeRPC( + pvAccessID /*ioid*/, epics::pvData::PVStructure::shared_pointer const & /*arguments*/) { + return epics::pvData::Status::Ok; + } + + // read: bitSet w/ one bit (allowed, not allowed) and rest of the bit per field + virtual epics::pvData::Status authorizeCreateMonitor( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizeMonitor(pvAccessID /*ioid*/) { + return epics::pvData::Status::Ok; + } + + // read: bitSet w/ one bit (allowed, not allowed) and rest put/get/set length + virtual epics::pvData::Status authorizeCreateChannelArray( + pvAccessID /*ioid*/, epics::pvData::PVStructure const & /*pvRequest*/) { + return epics::pvData::Status::Ok; + } + + // use authorizeGet + virtual epics::pvData::Status authorizePut( + pvAccessID /*ioid*/, epics::pvData::PVArray::shared_pointer const & /*dataToPut*/) { + return epics::pvData::Status::Ok; + } + + virtual epics::pvData::Status authorizeSetLength(pvAccessID /*ioid*/) { + return epics::pvData::Status::Ok; + } + + // introspection authorization + virtual epics::pvData::Status authorizeGetField(pvAccessID /*ioid*/, std::string const & /*subField*/) { + return epics::pvData::Status::Ok; + } + + }; + + class epicsShareClass AuthNZHandler : + public AbstractResponseHandler, + private epics::pvData::NoDefaultMethods + { + public: + AuthNZHandler(Context* context) : + AbstractResponseHandler(context, "authNZ message") + { + } + + virtual ~AuthNZHandler() {} + + virtual void handleResponse(osiSockAddr* responseFrom, + Transport::shared_pointer const & transport, + epics::pvData::int8 version, + epics::pvData::int8 command, + size_t payloadSize, + epics::pvData::ByteBuffer* payloadBuffer) + { + AbstractResponseHandler::handleResponse(responseFrom, transport, version, command, payloadSize, payloadBuffer); + + epics::pvData::PVField::shared_pointer data = + SerializationHelper::deserializeFull(payloadBuffer, transport.get()); + + transport->authNZMessage(data); + } + }; + + class epicsShareClass SecurityPluginRegistry : + private epics::pvData::NoDefaultMethods + { + public: + + static SecurityPluginRegistry& instance() + { + static SecurityPluginRegistry thisInstance; + return thisInstance; + } + + std::map >& getClientSecurityPlugins() + { + return m_clientSecurityPlugins; + } + + std::map >& getServerSecurityPlugins() + { + return m_serverSecurityPlugins; + } + + void installClientSecurityPlugin(std::tr1::shared_ptr plugin) + { + m_clientSecurityPlugins[plugin->getId()] = plugin; + } + + void installServerSecurityPlugin(std::tr1::shared_ptr plugin) + { + m_serverSecurityPlugins[plugin->getId()] = plugin; + } + + private: + SecurityPluginRegistry() {} + + std::map > m_clientSecurityPlugins; + std::map > m_serverSecurityPlugins; + }; + + + } +} + +#endif // SECURITY_H diff --git a/src/remote/serializationHelper.cpp b/src/remote/serializationHelper.cpp index dae7e9f..e003365 100644 --- a/src/remote/serializationHelper.cpp +++ b/src/remote/serializationHelper.cpp @@ -40,14 +40,19 @@ PVStructure::shared_pointer SerializationHelper::deserializeStructureAndCreatePV PVStructure::shared_pointer SerializationHelper::deserializeStructureFull(ByteBuffer* buffer, DeserializableControl* control) { - PVStructure::shared_pointer pvStructure; - FieldConstPtr structureField = control->cachedDeserialize(buffer); - if (structureField.get() != 0) + return std::tr1::static_pointer_cast(deserializeFull(buffer, control)); +} + +PVField::shared_pointer SerializationHelper::deserializeFull(ByteBuffer* buffer, DeserializableControl* control) +{ + PVField::shared_pointer pvField; + FieldConstPtr field = control->cachedDeserialize(buffer); + if (field.get() != 0) { - pvStructure = _pvDataCreate->createPVStructure(std::tr1::static_pointer_cast(structureField)); - pvStructure->deserialize(buffer, control); + pvField = _pvDataCreate->createPVField(field); + pvField->deserialize(buffer, control); } - return pvStructure; + return pvField; } void SerializationHelper::serializeNullField(ByteBuffer* buffer, SerializableControl* control) @@ -64,15 +69,20 @@ void SerializationHelper::serializePVRequest(ByteBuffer* buffer, SerializableCon void SerializationHelper::serializeStructureFull(ByteBuffer* buffer, SerializableControl* control, PVStructure::shared_pointer const & pvStructure) { - if (pvStructure.get() == 0) - { - serializeNullField(buffer, control); - } - else - { - control->cachedSerialize(pvStructure->getField(), buffer); - pvStructure->serialize(buffer, control); - } + serializeFull(buffer, control, pvStructure); +} + +void SerializationHelper::serializeFull(ByteBuffer* buffer, SerializableControl* control, PVField::shared_pointer const & pvField) +{ + if (pvField.get() == 0) + { + serializeNullField(buffer, control); + } + else + { + control->cachedSerialize(pvField->getField(), buffer); + pvField->serialize(buffer, control); + } } }} diff --git a/src/remote/serializationHelper.h b/src/remote/serializationHelper.h index c712e4a..3e31596 100644 --- a/src/remote/serializationHelper.h +++ b/src/remote/serializationHelper.h @@ -67,7 +67,19 @@ namespace epics { */ static epics::pvData::PVStructure::shared_pointer deserializeStructureFull(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control); - static void serializeNullField(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control); + /** + * Deserialize optional PVField. + * @param payloadBuffer data buffer. + * @return deserialized PVField, can be null. + */ + static epics::pvData::PVField::shared_pointer deserializeFull(epics::pvData::ByteBuffer* payloadBuffer, epics::pvData::DeserializableControl* control); + + /** + * Serialize null PVField. + * @param buffer + * @param control + */ + static void serializeNullField(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control); /** * Serialize PVRequest. @@ -81,6 +93,11 @@ namespace epics { */ static void serializeStructureFull(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, epics::pvData::PVStructure::shared_pointer const & pvStructure); + /** + * Serialize optional PVField. + * @param buffer data buffer. + */ + static void serializeFull(epics::pvData::ByteBuffer* buffer, epics::pvData::SerializableControl* control, epics::pvData::PVField::shared_pointer const & pvField); }; } diff --git a/src/remoteClient/clientContextImpl.cpp b/src/remoteClient/clientContextImpl.cpp index c5f3754..867a7fb 100644 --- a/src/remoteClient/clientContextImpl.cpp +++ b/src/remoteClient/clientContextImpl.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -47,9 +48,9 @@ namespace epics { namespace pvAccess { string ClientContextImpl::PROVIDER_NAME = "pva"; - Status ChannelImpl::channelDestroyed = Status( + Status ChannelImpl::channelDestroyed( Status::STATUSTYPE_WARNING, "channel destroyed"); - Status ChannelImpl::channelDisconnected = Status( + Status ChannelImpl::channelDisconnected( Status::STATUSTYPE_WARNING, "channel disconnected"); string emptyString; ConvertPtr convert = getConvert(); @@ -389,15 +390,15 @@ namespace epics { PVDataCreatePtr BaseRequestImpl::pvDataCreate = getPVDataCreate(); - Status BaseRequestImpl::notInitializedStatus = Status(Status::STATUSTYPE_ERROR, "request not initialized"); - Status BaseRequestImpl::destroyedStatus = Status(Status::STATUSTYPE_ERROR, "request destroyed"); - Status BaseRequestImpl::channelNotConnected = Status(Status::STATUSTYPE_ERROR, "channel not connected"); - Status BaseRequestImpl::channelDestroyed = Status(Status::STATUSTYPE_ERROR, "channel destroyed"); - Status BaseRequestImpl::otherRequestPendingStatus = Status(Status::STATUSTYPE_ERROR, "other request pending"); - Status BaseRequestImpl::invalidPutStructureStatus = Status(Status::STATUSTYPE_ERROR, "incompatible put structure"); - Status BaseRequestImpl::invalidPutArrayStatus = Status(Status::STATUSTYPE_ERROR, "incompatible put array"); - Status BaseRequestImpl::invalidBitSetLengthStatus = Status(Status::STATUSTYPE_ERROR, "invalid bit-set length"); - Status BaseRequestImpl::pvRequestNull = Status(Status::STATUSTYPE_ERROR, "pvRequest == 0"); + Status BaseRequestImpl::notInitializedStatus(Status::STATUSTYPE_ERROR, "request not initialized"); + Status BaseRequestImpl::destroyedStatus(Status::STATUSTYPE_ERROR, "request destroyed"); + Status BaseRequestImpl::channelNotConnected(Status::STATUSTYPE_ERROR, "channel not connected"); + Status BaseRequestImpl::channelDestroyed(Status::STATUSTYPE_ERROR, "channel destroyed"); + Status BaseRequestImpl::otherRequestPendingStatus(Status::STATUSTYPE_ERROR, "other request pending"); + Status BaseRequestImpl::invalidPutStructureStatus(Status::STATUSTYPE_ERROR, "incompatible put structure"); + Status BaseRequestImpl::invalidPutArrayStatus(Status::STATUSTYPE_ERROR, "incompatible put array"); + Status BaseRequestImpl::invalidBitSetLengthStatus(Status::STATUSTYPE_ERROR, "invalid bit-set length"); + Status BaseRequestImpl::pvRequestNull(Status::STATUSTYPE_ERROR, "pvRequest == 0"); PVStructure::shared_pointer BaseRequestImpl::nullPVStructure; Structure::const_shared_pointer BaseRequestImpl::nullStructure; @@ -2821,14 +2822,17 @@ namespace epics { // TODO // TODO serverIntrospectionRegistryMaxSize /*int serverIntrospectionRegistryMaxSize = */ payloadBuffer->getShort(); - // TODO authNZ - size_t size = SerializeHelper::readSize(payloadBuffer, transport.get()); - for (size_t i = 0; i < size; i++) - SerializeHelper::deserializeString(payloadBuffer, transport.get()); - TransportSender::shared_pointer sender = dynamic_pointer_cast(transport); - if (sender.get()) - transport->enqueueSendRequest(sender); + // authNZ + size_t size = SerializeHelper::readSize(payloadBuffer, transport.get()); + vector offeredSecurityPlugins; + offeredSecurityPlugins.reserve(size); + for (size_t i = 0; i < size; i++) + offeredSecurityPlugins.push_back( + SerializeHelper::deserializeString(payloadBuffer, transport.get()) + ); + + transport->authNZInitialize(&offeredSecurityPlugins); } }; @@ -2965,8 +2969,8 @@ namespace epics { m_handlerTable[CMD_ECHO].reset(new NoopResponse(context, "Echo")); /* 2 */ m_handlerTable[CMD_SEARCH].reset(new NoopResponse(context, "Search")); /* 3 */ m_handlerTable[CMD_SEARCH_RESPONSE].reset(new SearchResponseHandler(context)); /* 4 */ - m_handlerTable[CMD_AUTHNZ].reset(new NoopResponse(context, "Introspection search")); /* 5 */ - m_handlerTable[CMD_ACL_CHANGE] = dataResponse; /* 6 */ + m_handlerTable[CMD_AUTHNZ].reset(new AuthNZHandler(context.get())); /* 5 */ + m_handlerTable[CMD_ACL_CHANGE].reset(new NoopResponse(context, "Access rights change")); /* 6 */ m_handlerTable[CMD_CREATE_CHANNEL].reset(new CreateChannelHandler(context)); /* 7 */ m_handlerTable[CMD_DESTROY_CHANNEL].reset(new NoopResponse(context, "Destroy channel")); /* 8 */ // TODO it might be useful to implement this... m_handlerTable[CMD_CONNECTION_VALIDATED].reset(new ClientConnectionValidatedHandler(context)); /* 9 */ @@ -4631,6 +4635,11 @@ TODO // TODO } + std::map >& getSecurityPlugins() + { + return SecurityPluginRegistry::instance().getClientSecurityPlugins(); + } + /** * Get channel search manager. * @return channel search manager. diff --git a/src/server/baseChannelRequester.cpp b/src/server/baseChannelRequester.cpp index 7a91e99..9f68c76 100644 --- a/src/server/baseChannelRequester.cpp +++ b/src/server/baseChannelRequester.cpp @@ -13,13 +13,13 @@ namespace epics { namespace pvAccess { const Status BaseChannelRequester::okStatus = Status(); -const Status BaseChannelRequester::badCIDStatus = Status(Status::STATUSTYPE_ERROR, "bad channel id"); -const Status BaseChannelRequester::badIOIDStatus = Status(Status::STATUSTYPE_ERROR, "bad request id"); -const Status BaseChannelRequester::noReadACLStatus = Status(Status::STATUSTYPE_ERROR, "no read access"); -const Status BaseChannelRequester::noWriteACLStatus = Status(Status::STATUSTYPE_ERROR, "no write access"); -const Status BaseChannelRequester::noProcessACLStatus = Status(Status::STATUSTYPE_ERROR, "no process access"); -const Status BaseChannelRequester::otherRequestPendingStatus = Status(Status::STATUSTYPE_ERROR, "other request pending"); -const Status BaseChannelRequester::notAChannelRequestStatus = Status(Status::STATUSTYPE_ERROR, "not a channel request"); +const Status BaseChannelRequester::badCIDStatus(Status::STATUSTYPE_ERROR, "bad channel id"); +const Status BaseChannelRequester::badIOIDStatus(Status::STATUSTYPE_ERROR, "bad request id"); +const Status BaseChannelRequester::noReadACLStatus(Status::STATUSTYPE_ERROR, "no read access"); +const Status BaseChannelRequester::noWriteACLStatus(Status::STATUSTYPE_ERROR, "no write access"); +const Status BaseChannelRequester::noProcessACLStatus(Status::STATUSTYPE_ERROR, "no process access"); +const Status BaseChannelRequester::otherRequestPendingStatus(Status::STATUSTYPE_ERROR, "other request pending"); +const Status BaseChannelRequester::notAChannelRequestStatus(Status::STATUSTYPE_ERROR, "not a channel request"); const int32 BaseChannelRequester::NULL_REQUEST = -1; diff --git a/src/server/responseHandlers.cpp b/src/server/responseHandlers.cpp index a43c227..a462eff 100644 --- a/src/server/responseHandlers.cpp +++ b/src/server/responseHandlers.cpp @@ -19,6 +19,7 @@ #include #include +#include using std::string; using std::ostringstream; @@ -92,8 +93,8 @@ ServerResponseHandler::ServerResponseHandler(ServerContextImpl::shared_pointer c m_handlerTable[CMD_ECHO].reset(new ServerEchoHandler(context)); /* 2 */ m_handlerTable[CMD_SEARCH].reset(new ServerSearchHandler(context)); /* 3 */ m_handlerTable[CMD_SEARCH_RESPONSE] = badResponse; - m_handlerTable[CMD_AUTHNZ] = badResponse; /* 5 */ - m_handlerTable[CMD_ACL_CHANGE] = badResponse; /* 6 - introspection search */ + m_handlerTable[CMD_AUTHNZ].reset(new AuthNZHandler(context.get())); /* 5 */ + m_handlerTable[CMD_ACL_CHANGE] = badResponse; /* 6 - access right change */ m_handlerTable[CMD_CREATE_CHANNEL].reset(new ServerCreateChannelHandler(context)); /* 7 */ m_handlerTable[CMD_DESTROY_CHANNEL].reset(new ServerDestroyChannelHandler(context)); /* 8 */ m_handlerTable[CMD_CONNECTION_VALIDATED] = badResponse; /* 9 */ @@ -152,13 +153,28 @@ void ServerConnectionValidationHandler::handleResponse( /* int clientIntrospectionRegistryMaxSize = */ payloadBuffer->getShort(); // TODO connectionQoS /* int16 connectionQoS = */ payloadBuffer->getShort(); - // TODO authNZ - /*std::string authNZ = */ SerializeHelper::deserializeString(payloadBuffer, transport.get()); - // TODO call this after authNZ has done their work - transport->verified(Status::Ok); + // authNZ + std::string securityPluginName = SerializeHelper::deserializeString(payloadBuffer, transport.get()); + + // optional authNZ plug-in initialization data + PVField::shared_pointer data; + if (payloadBuffer->getRemaining()) + SerializationHelper::deserializeFull(payloadBuffer, transport.get()); + + struct { + std::string securityPluginName; + PVField::shared_pointer data; + } initData = { securityPluginName, data }; + + transport->authNZInitialize(&initData); } + + + + + void ServerEchoHandler::handleResponse(osiSockAddr* responseFrom, Transport::shared_pointer const & transport, int8 version, int8 command, size_t payloadSize, ByteBuffer* payloadBuffer) @@ -630,7 +646,7 @@ void ServerChannelRequesterImpl::channelCreated(const Status& status, Channel::s pvAccessID sid = casTransport->preallocateChannelSID(); try { - epics::pvData::PVField::shared_pointer securityToken = casTransport->getSecurityToken(); + epics::pvData::PVField::shared_pointer securityToken; // TODO replace with security session serverChannel.reset(new ServerChannelImpl(channel, _cid, sid, securityToken)); // ack allocation and register diff --git a/src/server/serverContext.cpp b/src/server/serverContext.cpp index b99f899..a95e1be 100644 --- a/src/server/serverContext.cpp +++ b/src/server/serverContext.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace std; using namespace epics::pvData; @@ -673,6 +674,11 @@ void ServerContextImpl::newServerDetected() // not used } +std::map >& ServerContextImpl::getSecurityPlugins() +{ + return SecurityPluginRegistry::instance().getServerSecurityPlugins(); +} + struct ThreadRunnerParam { diff --git a/src/server/serverContext.h b/src/server/serverContext.h index 66527cf..0cf6116 100644 --- a/src/server/serverContext.h +++ b/src/server/serverContext.h @@ -138,6 +138,7 @@ public: Transport::shared_pointer getSearchTransport(); Configuration::shared_pointer getConfiguration(); TransportRegistry::shared_pointer getTransportRegistry(); + std::map >& getSecurityPlugins(); std::auto_ptr createResponseHandler(); virtual void newServerDetected(); diff --git a/testApp/remote/testCodec.cpp b/testApp/remote/testCodec.cpp index 6bae4ec..d55ef62 100644 --- a/testApp/remote/testCodec.cpp +++ b/testApp/remote/testCodec.cpp @@ -357,6 +357,16 @@ namespace epics { void aliveNotification() {} + void authNZMessage(epics::pvData::PVField::shared_pointer const & data) {} + void authNZInitialize(void*) {} + + virtual std::tr1::shared_ptr getSecuritySession() const + { + return std::tr1::shared_ptr(); + } + + + bool isClosed() { return false; }