/*
* Open-iSNS policy engine
*
* Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
*
* For now, policy is static. We can make it configurable
* later.
*/
#include <string.h>
#include <stdlib.h>
#include "config.h"
#include <libisns/isns.h>
#include "security.h"
#include "objects.h"
#include <libisns/message.h>
#include <libisns/util.h>
/*
A brief discussion of policy
For now, a principal's name (ie its SPI string) *must* match
the iSNS source name it uses.
Special care needs to be taken to restrict which principals
are permitted to act as a control node. For now, we don't
implement control node semantics.
*/
static unsigned int isns_policy_gen = 0;
/*
* Administrative policy (everything allowed,
* talks to entity "CONTROL"
*/
static isns_policy_t isns_superhero_powers = {
.ip_name = "administrator",
.ip_users = 1,
.ip_gen = 0,
.ip_entity = ISNS_ENTITY_CONTROL,
.ip_functions = ~0,
.ip_object_types = ~0,
.ip_node_types = ~0,
};
/*
* Policy for anon user
*/
static isns_policy_t isns_dweeb_powers = {
.ip_name = "anonymous",
.ip_users = 1,
.ip_gen = 0,
.ip_functions = 1 << ISNS_DEVICE_ATTRIBUTE_QUERY,
.ip_object_types = 0,
.ip_node_types = 0,
};
#define IS_ANON_POLICY(p) ((p) == &isns_dweeb_powers)
/*
* These are used when security is turned off.
* Essentially the same as superhero, except
* no eid specified.
*/
static isns_policy_t isns_flyingpigs_powers = {
.ip_name = "insecure",
.ip_users = 1,
.ip_gen = 0,
.ip_functions = ~0,
.ip_object_types = ~0,
.ip_node_types = ~0,
};
isns_policy_t *
isns_policy_bind(const isns_message_t *msg)
{
isns_policy_t *policy = NULL;
isns_principal_t *princ = NULL;
/* When the admin turns off gravity,
* pigs can fly, too. */
if (isns_config.ic_security == 0) {
policy = &isns_flyingpigs_powers;
goto found;
}
/* If the caller is the local root user, s/he can
* do anything. */
if (msg->im_creds && msg->im_creds->CMSGCRED_uid == 0) {
policy = &isns_superhero_powers;
goto found;
}
/* Tie the SPI given in the auth block to a
* source name.
* For now, the names have to match. Down the road,
* there may be more flexible schemes.
*/
if ((princ = msg->im_security) != NULL) {
if ((policy = princ->is_policy) != NULL)
goto found;
isns_error("Internal error - no policy for "
"principal %s!\n",
princ->is_name);
}
policy = &isns_dweeb_powers;
found:
policy->ip_users++;
return policy;
}
/*
* Check whether the call is permitted.
* This is particularly useful to prevent rogue
* clients from messing with Discovery Domains.
*/
int
isns_policy_validate_function(const isns_policy_t *policy,
const isns_message_t *msg)
{
uint32_t function = msg->im_header.i_function;
int rv = 0;
if (function >= 32) {
isns_debug_auth("Bad function code %08x\n", function);
return 0;
}
if (!(policy->ip_functions & (1 << function)))
goto reject;
rv = 1;
reject:
isns_debug_auth(":: policy %s function %s (%04x) %s\n",
policy->ip_name,
isns_function_name(function), function,
rv? "permitted" : "DENIED");
return rv;
}
/*
* Helper function to validate node names and source names
*/
static int
__validate_node_name(const isns_policy_t *policy, const char *name)
{
const struct string_array *ap;
unsigned int i;
/* Control nodes get to do everything */
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
return 1;
ap = &policy->ip_node_names;
for (i = 0; i < ap->count; ++i) {
const char *s;
s = ap->list[i];
if (s == NULL)
continue;
if (isns_source_pattern_match(s, name))
return 1;
}
return 0;
}
/*
* Validate the source of a message
*/
int
isns_policy_validate_source(const isns_policy_t *policy,
const isns_source_t *source)
{
const char *src_name = isns_source_name(source);
int rv = 0;
if (!__validate_node_name(policy, src_name))
goto reject;
rv = 1;
reject:
isns_debug_auth(":: policy %s source %s %s\n",
policy->ip_name, src_name,
rv? "permitted" : "DENIED");
return rv;
}
/*
* Check whether the entity name specified by the client
* is actually his to use.
*/
int
isns_policy_validate_entity(const isns_policy_t *policy,
const char *eid)
{
int rv = 0, eidlen;
/* Control nodes get to do everything */
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
goto accept;
/* For anonymous clients, refuse any attempt to
* create an entity */
if (IS_ANON_POLICY(policy))
goto reject;
/* If no entity is assigned, this means the client
* is not permitted to specify its own entity name,
* and accept what we assign it.
*/
if (policy->ip_entity == NULL)
goto reject;
eidlen = strlen(policy->ip_entity);
if (strncasecmp(policy->ip_entity, eid, eidlen)
&& (eid[eidlen] == ':' || eid[eidlen] == '\0'))
goto reject;
accept: rv = 1;
reject:
isns_debug_auth(":: policy %s entity ID %s %s\n",
policy->ip_name, eid,
rv? "permitted" : "DENIED");
return rv;
}
const char *
isns_policy_default_entity(const isns_policy_t *policy)
{
return policy->ip_entity;
}
int
isns_policy_validate_node_name(const isns_policy_t *policy,
const char *node_name)
{
int rv = 0;
/* Control nodes get to do everything */
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
goto accept;
if (!__validate_node_name(policy, node_name))
goto reject;
accept: rv = 1;
reject:
isns_debug_auth(":: policy %s storage node name %s %s\n",
policy->ip_name, node_name,
rv? "permitted" : "DENIED");
return rv;
}
/*
* Check whether the client is allowed to access
* the given object in a particular way.
*/
static int
__isns_policy_validate_object_access(const isns_policy_t *policy,
const isns_source_t *source,
const isns_object_t *obj,
isns_object_template_t *tmpl,
unsigned int function)
{
uint32_t mask, perm = ISNS_PERMISSION_WRITE;
int rv = 0;
/* Control nodes get to do everything */
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
goto accept;
if (function == ISNS_DEVICE_ATTRIBUTE_QUERY
|| function == ISNS_DEVICE_GET_NEXT)
perm = ISNS_PERMISSION_READ;
/*
* 5.6.1. Source Attribute
*
* For messages that change the contents of the iSNS
* database, the iSNS server MUST verify that the Source
* Attribute identifies either a Control Node or a Storage
* Node that is a part of the Network Entity containing
* the added, deleted, or modified objects.
*
* Note: this statement makes sense for nodes, portals
* etc, but not for discovery domains, which are not
* part of any network entity (but the Control Node clause
* above still applies).
*/
if (perm == ISNS_PERMISSION_WRITE && obj != NULL) {
const isns_object_t *entity;
entity = obj->ie_container;
if (entity && entity != source->is_entity)
goto refuse;
/* You're not allowed to modify virtual objects */
if (obj->ie_rebuild)
goto refuse;
}
/* Check whether the client is permitted
to access such an object */
mask = ISNS_ACCESS(tmpl->iot_handle, perm);
if (!(policy->ip_object_types & mask))
goto refuse;
if (source->is_untrusted && (obj->ie_flags & ISNS_OBJECT_PRIVATE))
goto refuse;
accept:
rv = 1;
refuse:
if (obj) {
isns_debug_auth(":: policy %s operation %s on object %08x (%s) %s\n",
policy->ip_name,
isns_function_name(function),
obj->ie_index,
tmpl->iot_name,
rv? "permitted" : "DENIED");
} else {
isns_debug_auth(":: policy %s operation %s on %s object %s\n",
policy->ip_name,
isns_function_name(function),
tmpl->iot_name,
rv? "permitted" : "DENIED");
}
return rv;
}
/*
* Check whether the client is allowed to access
* the given object. This is called for read functions.
*/
int
isns_policy_validate_object_access(const isns_policy_t *policy,
const isns_source_t *source,
const isns_object_t *obj,
unsigned int function)
{
return __isns_policy_validate_object_access(policy, source,
obj, obj->ie_template,
function);
}
/*
* Check whether the client is allowed to update
* the given object.
*/
int
isns_policy_validate_object_update(const isns_policy_t *policy,
const isns_source_t *source,
const isns_object_t *obj,
const isns_attr_list_t *attrs,
unsigned int function)
{
return __isns_policy_validate_object_access(policy, source,
obj, obj->ie_template,
function);
}
/*
* Check whether the client is allowed to create an object
* with the given attrs.
*/
int
isns_policy_validate_object_creation(const isns_policy_t *policy,
const isns_source_t *source,
isns_object_template_t *tmpl,
const isns_attr_list_t *keys,
const isns_attr_list_t *attrs,
unsigned int function)
{
const char *name = NULL;
if (tmpl == &isns_entity_template) {
/* DevReg messages may contain an empty EID
* string, which means the server should select
* one. */
if (isns_attr_list_get_string(keys,
ISNS_TAG_ENTITY_IDENTIFIER, &name)
&& !isns_policy_validate_entity(policy, name))
return 0;
}
if (tmpl == &isns_iscsi_node_template) {
if (isns_attr_list_get_string(keys,
ISNS_TAG_ISCSI_NAME, &name)
&& !isns_policy_validate_node_name(policy, name))
return 0;
}
/* Should we also include the permitted portals
* in the policy? */
return __isns_policy_validate_object_access(policy, source,
NULL, tmpl, function);
}
/*
* Check whether the client is permitted to access
* or create an object of this type.
* FIXME: Pass R/W permission bit
*/
int
isns_policy_validate_object_type(const isns_policy_t *policy,
isns_object_template_t *tmpl,
unsigned int function)
{
uint32_t mask;
int rv = 0;
/* Control nodes get to do everything */
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
goto accept;
mask = ISNS_ACCESS_R(tmpl->iot_handle);
if (!(policy->ip_object_types & mask))
goto reject;
accept: rv = 1;
reject:
isns_debug_auth(":: policy %s operation %s on object type %s %s\n",
policy->ip_name,
isns_function_name(function),
tmpl->iot_name,
rv? "permitted" : "DENIED");
return rv;
}
int
isns_policy_validate_node_type(const isns_policy_t *policy, uint32_t type)
{
int rv = 0;
if ((~policy->ip_node_types & type) == 0)
rv = 1;
isns_debug_auth(":: policy %s registration of node type 0x%x %s\n",
policy->ip_name, type,
rv? "permitted" : "DENIED");
return rv;
}
/*
* 6.4.4.
* Management SCNs provide information about all changes to the network,
* regardless of discovery domain membership. Registration for management
* SCNs is indicated by setting bit 26 to 1. Only Control Nodes may
* register for management SCNs. Bits 30 and 31 may only be enabled if
* bit 26 is set to 1.
*/
int
isns_policy_validate_scn_bitmap(const isns_policy_t *policy,
uint32_t bitmap)
{
int rv = 1;
if (policy->ip_node_types & ISNS_ISCSI_CONTROL_MASK)
goto accept;
if (!(bitmap & ISNS_SCN_MANAGEMENT_REGISTRATION_MASK)) {
if (bitmap & (ISNS_SCN_DD_MEMBER_ADDED_MASK |
ISNS_SCN_DD_MEMBER_REMOVED_MASK))
goto reject;
goto accept;
}
reject:
rv = 0;
accept:
isns_debug_auth(":: policy %s scn bitmap 0x%x %s\n",
policy->ip_name, bitmap,
rv? "permitted" : "DENIED");
return rv;
}
/*
* Create the default policy for a given SPI
*/
isns_policy_t *
isns_policy_default(const char *spi, size_t len)
{
return __isns_policy_alloc(spi, len);
}
/*
* Create the policy object for the server we're
* talking to. The server is allowed to send us
* ESI and SCN messages, and that's about it.
*/
isns_policy_t *
isns_policy_server(void)
{
isns_policy_t *policy;
policy = __isns_policy_alloc("<server>", 8);
policy->ip_functions =
(1 << ISNS_ENTITY_STATUS_INQUIRY) |
(1 << ISNS_STATE_CHANGE_NOTIFICATION);
policy->ip_node_types = 0;
policy->ip_object_types = 0;
isns_string_array_append(&policy->ip_node_names, "*");
return policy;
}
/*
* Allocate an empty policy object
*/
isns_policy_t *
__isns_policy_alloc(const char *spi, size_t len)
{
isns_policy_t *policy;
policy = isns_calloc(1, sizeof(*policy));
policy->ip_name = isns_malloc(len + 1);
policy->ip_users = 1;
policy->ip_gen = isns_policy_gen;
memcpy(policy->ip_name, spi, len);
policy->ip_name[len] = '\0';
/* Only register/query allowed */
policy->ip_functions =
(1 << ISNS_DEVICE_ATTRIBUTE_REGISTER) |
(1 << ISNS_DEVICE_ATTRIBUTE_QUERY) |
(1 << ISNS_DEVICE_GET_NEXT) |
(1 << ISNS_DEVICE_DEREGISTER) |
(1 << ISNS_SCN_REGISTER);
/* Can only register initiator node(s) */
policy->ip_node_types =
ISNS_ISCSI_INITIATOR_MASK;
/* Can only view/modify standard objects */
policy->ip_object_types = ISNS_DEFAULT_OBJECT_ACCESS;
return policy;
}
/*
* Release a policy object
*/
void
isns_policy_release(isns_policy_t *policy)
{
if (!policy)
return;
isns_assert(policy->ip_users);
if (--(policy->ip_users))
return;
isns_assert(policy != &isns_superhero_powers);
isns_assert(policy != &isns_flyingpigs_powers);
isns_assert(policy != &isns_dweeb_powers);
isns_free(policy->ip_name);
isns_free(policy->ip_entity);
isns_free(policy->ip_dd_default);
isns_string_array_destroy(&policy->ip_node_names);
isns_free(policy);
}