Merge branch 'auth'

* auth:
  fix osdGetRoles
  oops
  Add showauth executable
  getgrouplist() on OSX has different sig.
  auth fix mingw static
  fix search PeerInfo
  PeerInfo in pva/server.h and pva/sharedstate.h
  make PeerInfo available during search phase
  auth status void accidental success.
  minor doc
  always need to link with netapi32 now
  authorize with local group lookup
  Add osdGetRoles()
  authorization framework
  pvasr show PeerInfo
  redo security (aka. access control)
  remove asCheck

# Conflicts:
#	src/server/pv/responseHandlers.h
#	src/server/responseHandlers.cpp
This commit is contained in:
Michael Davidsaver
2019-05-12 14:40:29 -07:00
38 changed files with 1139 additions and 911 deletions

View File

@@ -40,6 +40,8 @@ using namespace std;
using namespace epics::pvData;
using namespace epics::pvAccess;
typedef epicsGuard<epicsMutex> Guard;
namespace {
struct BreakTransport : TransportSender
{
@@ -1087,15 +1089,11 @@ void BlockingTCPTransportCodec::internalClose()
Transport::shared_pointer thisSharedPtr = this->shared_from_this();
_context->getTransportRegistry()->remove(thisSharedPtr);
// TODO sync
if (_securitySession)
_securitySession->close();
if (IS_LOGGABLE(logLevelDebug))
{
LOG(logLevelDebug,
"TCP socket to %s is to be closed.",
inetAddressToString(_socketAddress).c_str());
_socketName.c_str());
}
}
@@ -1244,7 +1242,6 @@ BlockingTCPTransportCodec::BlockingTCPTransportCodec(bool serverFlag, const Cont
ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr));
_socketName = ipAddrStr;
}
}
@@ -1361,24 +1358,28 @@ bool BlockingTCPTransportCodec::verify(epics::pvData::int32 timeoutMs) {
}
void BlockingTCPTransportCodec::verified(epics::pvData::Status const & status) {
epics::pvData::Lock lock(_verifiedMutex);
epics::pvData::Lock lock(_mutex);
if (IS_LOGGABLE(logLevelDebug) && !status.isOK())
{
char ipAddrStr[48];
ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr));
LOG(logLevelDebug, "Failed to verify connection to %s: %s.", ipAddrStr, status.getMessage().c_str());
// TODO stack dump
LOG(logLevelDebug, "Failed to verify connection to %s: %s.", _socketName.c_str(), status.getMessage().c_str());
}
_verified = status.isSuccess();
{
Guard G(_mutex);
_verified = status.isSuccess();
}
_verifiedEvent.signal();
}
void BlockingTCPTransportCodec::authNZMessage(epics::pvData::PVField::shared_pointer const & data) {
// TODO sync
if (_securitySession)
_securitySession->messageReceived(data);
void BlockingTCPTransportCodec::authNZMessage(epics::pvData::PVStructure::shared_pointer const & data) {
AuthenticationSession::shared_pointer sess;
{
Guard G(_mutex);
sess = _authSession;
}
if (sess)
sess->messageReceived(data);
else
{
char ipAddrStr[48];
@@ -1392,7 +1393,7 @@ class SecurityPluginMessageTransportSender : public TransportSender {
public:
POINTER_DEFINITIONS(SecurityPluginMessageTransportSender);
SecurityPluginMessageTransportSender(PVField::shared_pointer const & data) :
SecurityPluginMessageTransportSender(PVStructure::const_shared_pointer const & data) :
_data(data)
{
}
@@ -1405,11 +1406,10 @@ public:
}
private:
PVField::shared_pointer _data;
PVStructure::const_shared_pointer _data;
};
void BlockingTCPTransportCodec::sendSecurityPluginMessage(epics::pvData::PVField::shared_pointer const & data) {
// TODO not optimal since it allocates a new object every time
void BlockingTCPTransportCodec::sendSecurityPluginMessage(epics::pvData::PVStructure::const_shared_pointer const & data) {
SecurityPluginMessageTransportSender::shared_pointer spmts(new SecurityPluginMessageTransportSender(data));
enqueueSendRequest(spmts);
}
@@ -1423,10 +1423,12 @@ BlockingServerTCPTransportCodec::BlockingServerTCPTransportCodec(
SOCKET channel,
ResponseHandler::shared_pointer const & responseHandler,
int32_t sendBufferSize,
int32_t receiveBufferSize) :
BlockingTCPTransportCodec(true, context, channel, responseHandler,
sendBufferSize, receiveBufferSize, PVA_DEFAULT_PRIORITY),
_lastChannelSID(0), _verifyOrVerified(false), _securityRequired(false)
int32_t receiveBufferSize)
:BlockingTCPTransportCodec(true, context, channel, responseHandler,
sendBufferSize, receiveBufferSize, PVA_DEFAULT_PRIORITY)
,_lastChannelSID(0)
,_verificationStatus(pvData::Status::fatal("Uninitialized error"))
,_verifyOrVerified(false)
{
// NOTE: priority not yet known, default priority is used to
//register/unregister
@@ -1530,29 +1532,38 @@ void BlockingServerTCPTransportCodec::send(ByteBuffer* buffer,
// TODO
buffer->putShort(0x7FFF);
// list of authNZ plugin names
const Context::securityPlugins_t& securityPlugins = _context->getSecurityPlugins();
vector<string> validSPNames;
validSPNames.reserve(securityPlugins.size());
// list of authNZ plugin names advertised to this client
for (Context::securityPlugins_t::const_iterator iter(securityPlugins.begin());
iter != securityPlugins.end(); iter++)
AuthenticationRegistry::list_t plugins;
AuthenticationRegistry::servers().snapshot(plugins); // copy
std::vector<std::string> validSPNames;
validSPNames.reserve(plugins.size()); // assume all will be valid
PeerInfo info;
info.transport = "pva";
info.peer = _socketName;
info.transportVersion = this->getRevision();
// filter plugins which may be used by this peer
for(AuthenticationRegistry::list_t::iterator it(plugins.begin()), end(plugins.end());
it!=end; ++it)
{
SecurityPlugin::shared_pointer securityPlugin = iter->second;
if (securityPlugin->isValidFor(_socketAddress))
validSPNames.push_back(securityPlugin->getId());
info.authority = it->first;
if(it->second->isValidFor(info))
validSPNames.push_back(it->first);
}
size_t validSPCount = validSPNames.size();
SerializeHelper::writeSize(validSPCount, buffer, this);
for (vector<string>::const_iterator iter =
validSPNames.begin();
iter != validSPNames.end(); iter++)
SerializeHelper::writeSize(validSPNames.size(), buffer, this);
for (vector<string>::const_iterator iter(validSPNames.begin()), end(validSPNames.end());
iter != end; iter++)
{
SerializeHelper::serializeString(*iter, buffer, this);
}
// TODO sync
_securityRequired = (validSPCount > 0);
{
Guard G(_mutex);
advertisedAuthPlugins.swap(validSPNames);
}
// send immediately
control->flush(true);
@@ -1564,10 +1575,12 @@ void BlockingServerTCPTransportCodec::send(ByteBuffer* buffer,
//
control->startMessage(CMD_CONNECTION_VALIDATED, 0);
pvData::Status sts;
{
Lock lock(_verificationStatusMutex);
_verificationStatus.serialize(buffer, control);
Lock lock(_mutex);
sts = _verificationStatus;
}
sts.serialize(buffer, control);
// send immediately
control->flush(true);
@@ -1600,14 +1613,28 @@ void BlockingServerTCPTransportCodec::internalClose() {
destroyAllChannels();
}
void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status)
void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status,
const std::tr1::shared_ptr<PeerInfo>& peer)
{
if (IS_LOGGABLE(logLevelDebug))
{
LOG(logLevelDebug, "Authentication completed with status '%s' for PVA client: %s.", Status::StatusTypeName[status.getType()], _socketName.c_str());
}
if (!isVerified()) // TODO sync
if(peer)
AuthorizationRegistry::plugins().run(peer);
bool isVerified;
{
Guard G(_mutex);
isVerified = _verified;
if(status.isSuccess())
_peerInfo = peer;
else
_peerInfo.reset();
}
if (!isVerified)
verified(status);
else if (!status.isSuccess())
{
@@ -1620,59 +1647,35 @@ void BlockingServerTCPTransportCodec::authenticationCompleted(epics::pvData::Sta
}
}
epics::pvData::Status BlockingServerTCPTransportCodec::invalidSecurityPluginNameStatus(Status::STATUSTYPE_ERROR, "invalid security plug-in name");
void BlockingServerTCPTransportCodec::authNZInitialize(const std::string& securityPluginName,
const epics::pvData::PVField::shared_pointer& data)
const epics::pvData::PVStructure::shared_pointer& data)
{
// check if plug-in name is valid
SecurityPlugin::shared_pointer securityPlugin;
AuthenticationPlugin::shared_pointer plugin(AuthenticationRegistry::servers().lookup(securityPluginName));
// attempting the force use of an un-advertised/non-existant plugin is treated as a protocol error.
// We cheat here by assuming the the registry doesn't often change after server start,
// and don't test if securityPluginName is in advertisedAuthPlugins
if(!plugin)
throw std::runtime_error(_socketName+" failing attempt to select non-existant auth. plugin "+securityPluginName);
Context::securityPlugins_t::const_iterator spIter(_context->getSecurityPlugins().find(securityPluginName));
if (spIter != _context->getSecurityPlugins().end())
securityPlugin = spIter->second;
if (!securityPlugin)
{
if (_securityRequired)
{
verified(invalidSecurityPluginNameStatus);
return;
}
else
{
securityPlugin = NoSecurityPlugin::INSTANCE;
PeerInfo::shared_pointer info(new PeerInfo);
info->peer = _socketName;
info->transport = "pva";
info->transportVersion = getRevision();
info->authority = securityPluginName;
if (IS_LOGGABLE(logLevelDebug))
{
LOG(logLevelDebug, "No security plug-in installed, selecting default plug-in '%s' for PVA client: %s.", securityPlugin->getId().c_str(), _socketName.c_str());
}
}
}
if (!securityPlugin->isValidFor(_socketAddress))
verified(invalidSecurityPluginNameStatus);
if (!plugin->isValidFor(*info))
verified(pvData::Status::error("invalid security plug-in name"));
if (IS_LOGGABLE(logLevelDebug))
{
char ipAddrStr[48];
ipAddrToDottedIP(&_socketAddress.ia, ipAddrStr, sizeof(ipAddrStr));
LOG(logLevelDebug, "Accepted security plug-in '%s' for PVA client: %s.", securityPluginName.c_str(), ipAddrStr);
LOG(logLevelDebug, "Accepted security plug-in '%s' for PVA client: %s.", securityPluginName.c_str(), _socketName.c_str());
}
try
{
// create session
SecurityPluginControl::shared_pointer spc = std::tr1::dynamic_pointer_cast<SecurityPluginControl>(shared_from_this());
// TODO sync
_securitySession = securityPlugin->createSession(_socketAddress, spc, data);
} catch (SecurityException &se) {
if (IS_LOGGABLE(logLevelDebug))
{
LOG(logLevelDebug, "Security plug-in '%s' failed to create a session for PVA client: %s.", securityPluginName.c_str(), _socketName.c_str());
}
Status status(Status::STATUSTYPE_ERROR, se.what());
verified(status);
}
AuthenticationSession::shared_pointer sess(plugin->createSession(info, shared_from_this(), data));
Guard G(_mutex);
_authSessionName = securityPluginName;
_authSession.swap(sess);
}
@@ -1894,17 +1897,25 @@ void BlockingClientTCPTransportCodec::send(ByteBuffer* buffer,
// QoS (aka connection priority)
buffer->putShort(getPriority());
// TODO sync
if (_securitySession)
std::string pluginName;
AuthenticationSession::shared_pointer session;
{
Guard G(_mutex);
pluginName = _authSessionName;
session = _authSession;
}
if (session)
{
// selected authNZ plug-in name
SerializeHelper::serializeString(_securitySession->getSecurityPlugin()->getId(), buffer, control);
SerializeHelper::serializeString(_authSessionName, buffer, control);
// optional authNZ plug-in initialization data
SerializationHelper::serializeFull(buffer, control, _securitySession->initializationData());
SerializationHelper::serializeFull(buffer, control, session->initializationData());
}
else
{
//TODO: allowed?
// emptry authNZ plug-in name
SerializeHelper::serializeString("", buffer, control);
@@ -1926,38 +1937,68 @@ void BlockingClientTCPTransportCodec::send(ByteBuffer* buffer,
void BlockingClientTCPTransportCodec::authNZInitialize(const std::vector<std::string>& offeredSecurityPlugins)
{
if (!offeredSecurityPlugins.empty())
AuthenticationRegistry& plugins = AuthenticationRegistry::clients();
std::string selectedName;
AuthenticationPlugin::shared_pointer plugin;
// because of a missing break; the original SecurityPlugin effectively treated the offered list as being
// in order of increasing preference (last is preferred).
// we continue with this because, hey isn't compatibility fun...
for(std::vector<std::string>::const_reverse_iterator it(offeredSecurityPlugins.rbegin()), end(offeredSecurityPlugins.rend());
it!=end; ++it)
{
const Context::securityPlugins_t& availableSecurityPlugins(_context->getSecurityPlugins());
for (vector<string>::const_iterator offeredSP = offeredSecurityPlugins.begin();
offeredSP != offeredSecurityPlugins.end(); offeredSP++)
{
Context::securityPlugins_t::const_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<SecurityPluginControl>(shared_from_this());
// TODO sync
_securitySession = securityPlugin->createSession(_socketAddress, spc, PVField::shared_pointer());
}
}
plugin = plugins.lookup(*it);
if(plugin) {
selectedName = *it;
break;
}
}
if(!plugin) {
// mis-match and legacy. some early servers (java?) don't advertise any plugins.
// treat this as anonymous
selectedName = "anonymous";
plugin = plugins.lookup(selectedName);
assert(plugin); // fallback required
}
{
PeerInfo::shared_pointer info(new PeerInfo);
info->peer = _socketName; // this is the server name
info->transport = "pva";
info->transportVersion = getRevision();
info->authority = selectedName;
AuthenticationSession::shared_pointer sess(plugin->createSession(info, shared_from_this(), pvData::PVStructure::shared_pointer()));
Guard G(_mutex);
_authSessionName = selectedName;
_authSession = sess;
}
TransportSender::shared_pointer transportSender = std::tr1::dynamic_pointer_cast<TransportSender>(shared_from_this());
enqueueSendRequest(transportSender);
}
void BlockingClientTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status)
void BlockingClientTCPTransportCodec::authenticationCompleted(epics::pvData::Status const & status,
const std::tr1::shared_ptr<PeerInfo>& peer)
{
// noop for client side (server will send ConnectionValidation message)
}
void BlockingClientTCPTransportCodec::verified(epics::pvData::Status const & status)
{
AuthenticationSession::shared_pointer sess;
{
Guard G(_mutex);
sess = _authSession;
}
if(sess)
sess->authenticationComplete(status);
this->BlockingTCPTransportCodec::verified(status);
}
}
}
}