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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user