Merge remote-tracking branch 'lp-asLib/as-hostname' into 7.0

* lp-asLib/as-hostname:
  update release notes
  asLib more string size...
  asLib one short
  asLib: asUseIP name lookup soft-fail
  Expand Release Note entry for as-hostname changes.
  as-hostname address review comments
  asLib: test asUseIP
  as,rsrv: use real client IP instead of untrusted host name

# Conflicts:
#	documentation/RELEASE_NOTES.html
This commit is contained in:
Michael Davidsaver
2019-08-31 15:07:22 -07:00
8 changed files with 153 additions and 12 deletions

View File

@@ -30,6 +30,51 @@ release.</p>
-->
<h3>Channel Access Security: Check Hostname Against DNS</h3>
<p>Host names given in a <tt>HAG</tt> entry of an IOC's Access Security
Configuration File (ACF) have to date been compared against the hostname
provided by the CA client at connection time, which may or may not be the actual
name of that client. This allows rogue clients to pretend to be a different
host, and the IOC would believe them.</p>
<p>An option is now available to cause an IOC to ask its operating system to
look up the IP address of any hostnames listed in its ACF (which will normally
be done using the DNS or the <tt>/etc/hosts</tt> file). The IOC will then
compare the resulting IP address against the client's actual IP address when
checking access permissions at connection time. This name resolution is performed
at ACF file load time, which has a few consequences:</p>
<ol>
<li>If the DNS is slow when the names are resolved this will delay the process
of loading the ACF file.</li>
<li>If a host name cannot be resolved the IOC will proceed, but this host name
will never be matched.</li>
<li>Any changes in the hostname to IP address mapping will not be picked up by
the IOC unless and until the ACF file gets reloaded.</li>
</ol>
<p>Optionally, IP addresses may be added instead of, or in addition to, host names
in the ACF file.</p>
<p>This feature can be enabled before <tt>iocInit</tt> with</p>
<blockquote><pre>
var("asCheckClientIP",1)
</pre></blockquote>
<p>or with the VxWorks target shell use</p>
<blockquote><pre>
asCheckClientIP = 1
</pre></blockquote>
<h3>New and modified epicsThread APIs</h3>
<h4><code>epicsThreadCreateOpt()</code></h4>

View File

@@ -861,6 +861,14 @@ static int host_name_action ( caHdrLargeArray *mp, void *pPayload,
return RSRV_ERROR;
}
/* after all validation */
if(asCheckClientIP) {
DLOG (2, ( "CAS: host_name_action for \"%s\" ignores client provided host name\n",
client->pHostName ) );
return RSRV_OK;
}
/*
* user name will not change if there isnt enough memory
*/

View File

@@ -1421,6 +1421,20 @@ struct client *create_tcp_client (SOCKET sock , const osiSockAddr *peerAddr)
}
client->addr = peerAddr->ia;
if(asCheckClientIP) {
epicsUInt32 ip = ntohl(client->addr.sin_addr.s_addr);
client->pHostName = malloc(24);
if(!client->pHostName) {
destroy_client ( client );
return NULL;
}
epicsSnprintf(client->pHostName, 24,
"%u.%u.%u.%u",
(ip>>24)&0xff,
(ip>>16)&0xff,
(ip>>8)&0xff,
(ip>>0)&0xff);
}
/*
* see TCP(4P) this seems to make unsolicited single events much

View File

@@ -86,7 +86,7 @@ typedef struct client {
ELLLIST chanList;
ELLLIST chanPendingUpdateARList;
ELLLIST putNotifyQue;
struct sockaddr_in addr;
struct sockaddr_in addr; /* peer address, TCP only */
epicsTimeStamp time_at_last_send;
epicsTimeStamp time_at_last_recv;
void *evuser;

View File

@@ -21,6 +21,11 @@
extern "C" {
#endif
/* 0 - Use (unverified) client provided host name string.
* 1 - Use actual client IP address. HAG() are resolved to IPs at ACF load time.
*/
epicsShareExtern int asCheckClientIP;
typedef struct asgMember *ASMEMBERPVT;
typedef struct asgClient *ASCLIENTPVT;
typedef int (*ASINPUTFUNCPTR)(char *buf,int max_size);
@@ -165,8 +170,8 @@ typedef struct uag{
} UAG;
/*Defs for Host Access Groups*/
typedef struct{
ELLNODE node;
char *host;
ELLNODE node;
char host[1];
} HAGNAME;
typedef struct hag{
ELLNODE node;

View File

@@ -15,6 +15,8 @@
#include <ctype.h>
#define epicsExportSharedSymbols
#include "osiSock.h"
#include "epicsTypes.h"
#include "epicsStdio.h"
#include "dbDefs.h"
#include "epicsThread.h"
@@ -27,6 +29,8 @@
#include "postfix.h"
#include "asLib.h"
int asCheckClientIP;
static epicsMutexId asLock;
#define LOCK epicsMutexMustLock(asLock)
#define UNLOCK epicsMutexUnlock(asLock)
@@ -1203,14 +1207,38 @@ static HAG *asHagAdd(const char *hagName)
static long asHagAddHost(HAG *phag,const char *host)
{
HAGNAME *phagname;
int len, i;
if (!phag) return 0;
len = strlen(host);
phagname = asCalloc(1, sizeof(HAGNAME) + len + 1);
phagname->host = (char *)(phagname + 1);
for (i = 0; i < len; i++) {
phagname->host[i] = (char)tolower((int)host[i]);
if(!asCheckClientIP) {
size_t i, len = strlen(host);
phagname = asCalloc(1, sizeof(HAGNAME) + len);
for (i = 0; i < len; i++) {
phagname->host[i] = (char)tolower((int)host[i]);
}
} else {
struct sockaddr_in addr;
epicsUInt32 ip;
if(aToIPAddr(host, 0, &addr)) {
static const char unresolved[] = "unresolved:";
errlogPrintf("ACF: Unable to resolve host '%s'\n", host);
phagname = asCalloc(1, sizeof(HAGNAME) + sizeof(unresolved)-1+strlen(host));
strcpy(phagname->host, unresolved);
strcat(phagname->host, host);
} else {
ip = ntohl(addr.sin_addr.s_addr);
phagname = asCalloc(1, sizeof(HAGNAME) + 24);
epicsSnprintf(phagname->host, 24,
"%u.%u.%u.%u",
(ip>>24)&0xff,
(ip>>16)&0xff,
(ip>>8)&0xff,
(ip>>0)&0xff);
}
}
ellAdd(&phag->list, &phagname->node);
return 0;

View File

@@ -12,6 +12,7 @@
#define epicsExportSharedSymbols
#include "iocsh.h"
#include "asLib.h"
#include "epicsStdioRedirect.h"
#include "epicsString.h"
#include "epicsTime.h"
@@ -393,6 +394,8 @@ static void installLastResortEventProviderCallFunc(const iocshArgBuf *args)
installLastResortEventProvider();
}
static iocshVarDef asCheckClientIPDef = {"asCheckClientIP", iocshArgInt, 0};
void epicsShareAPI libComRegister(void)
{
iocshRegister(&dateFuncDef, dateCallFunc);
@@ -425,4 +428,7 @@ void epicsShareAPI libComRegister(void)
iocshRegister(&generalTimeReportFuncDef,generalTimeReportCallFunc);
iocshRegister(&installLastResortEventProviderFuncDef, installLastResortEventProviderCallFunc);
asCheckClientIPDef.pval = &asCheckClientIP;
iocshRegisterVariable(&asCheckClientIPDef);
}

View File

@@ -81,8 +81,8 @@ static const char hostname_config[] = ""
static void testHostNames(void)
{
testDiag("testHostNames()");
asCheckClientIP = 0;
testOk1(asInitMem(hostname_config, NULL)==0);
@@ -102,18 +102,53 @@ static void testHostNames(void)
testAccess("ro", 0);
testAccess("rw", 0);
setHost("nosuchhost");
setHost("guaranteed.invalid.");
testAccess("invalid", 0);
testAccess("DEFAULT", 0);
testAccess("ro", 0);
testAccess("rw", 0);
}
static void testUseIP(void)
{
testDiag("testUseIP()");
asCheckClientIP = 1;
/* still host names in .acf */
testOk1(asInitMem(hostname_config, NULL)==0);
/* now resolved to IPs */
setUser("testing");
setHost("localhost"); /* will not match against resolved IP */
asAsl = 0;
testAccess("invalid", 0);
testAccess("DEFAULT", 0);
testAccess("ro", 0);
testAccess("rw", 0);
setHost("127.0.0.1");
testAccess("invalid", 0);
testAccess("DEFAULT", 0);
testAccess("ro", 1);
testAccess("rw", 3);
setHost("guaranteed.invalid.");
testAccess("invalid", 0);
testAccess("DEFAULT", 0);
testAccess("ro", 0);
testAccess("rw", 0);
}
MAIN(aslibtest)
{
testPlan(14);
testPlan(27);
testSyntaxErrors();
testHostNames();
testUseIP();
errlogFlush();
return testDone();
}