Policies in yaml

Implement functions allowing to define object_level policies in
model's yaml file

Change-Id: I4a4b70edf95c56d8dba7ee669d5ccc8bd387c4d8
This commit is contained in:
JinLi 2017-07-21 15:28:13 -07:00
parent 224ebeab8f
commit ecd29999cb
11 changed files with 420 additions and 9 deletions

View File

@ -1,4 +1,9 @@
{
"COMMENT": "This file is no longer needed, but for historical record !!!",
"COMMENT": "The policy.json file in /etc/proton directory should contain",
"COMMENT": "empty json object: {}",
"COMMENT": "This first part is moved to code in policies/base.py",
"context_is_admin": "role:admin",
"owner": "tenant_id:%(tenant_id)s",
"admin_or_owner": "rule:context_is_admin or rule:owner",
@ -9,6 +14,7 @@
"regular_user": "",
"default": "rule:admin_or_owner",
"COMMENT": "The rest of policies are defined in YAML",
"create_ports": "rule:admin_or_network_owner",
"get_ports": "rule:admin_or_owner",
"update_ports": "rule:admin_or_network_owner",

View File

@ -13,14 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import webob
from oslo_config import cfg
from oslo_policy import policy as oslo_policy
# from oslo_utils import excutils
from pecan import hooks
from pecan.routing import lookup_controller
from gluon import constants as gluon_constants
from gluon.particleGenerator.ApiGenerator import API_OBJECT_CLASSES
from gluon import policy
# from gluon._i18n import _
@ -37,7 +40,7 @@ class PolicyHook(hooks.PecanHook):
if state.request.method not in ('GET', 'POST', 'PUT', 'DELETE'):
return
method = gluon_constants.ACTION_MAP[state.request.method]
method = generateMethod(state)
path_info = state.request.path_info
@ -49,15 +52,17 @@ class PolicyHook(hooks.PecanHook):
if not resource:
return
action = "%s_%s" % (method, resource)
service = path_info.split("/")[2]
action = "%s:%s_%s" % (service, method, resource)
gluon_context = state.request.context.get('gluon_context')
policy.init()
target = generateTarget(state, service, resource)
try:
policy.enforce(
gluon_context, action, None)
gluon_context, action, target)
except oslo_policy.PolicyNotAuthorized as e:
raise webob.exc.HTTPForbidden(str(e))
@ -65,3 +70,49 @@ class PolicyHook(hooks.PecanHook):
# This method could be used for implementing access control
# at the attribute level.
return
# The policy enforce function requires target parameter
# oslo_policy doc descripbes target param as: "As much information about the
# object being operated on as possible"
# For delete and get, prefetch data from database and put tenant_id into target
# For post and put, get all user inputs from request body and put into target
def generateTarget(state, service, resource):
target = {}
method = state.request.method
if method in ('GET', 'DELETE') and state.arguments.args:
api_object_class = API_OBJECT_CLASSES[service][resource]
key = state.arguments.args[0]
obj = api_object_class.get_from_db(key)
tenant_id = obj.tenant_id
target['tenant_id'] = tenant_id
if method in ('POST', 'PUT'):
request_body = json.loads(state.request.body)
target = request_body
return target
# method could be 'get' or 'list' for the http get method
# If there is a key specified, method is 'get'
# If there is no key specified, method is 'list
def generateMethod(state):
method = state.request.method
if method == 'GET' and not state.arguments.args:
method = 'list'
else:
method = gluon_constants.ACTION_MAP[state.request.method]
return method
def findContrller(state):
path = state.request.path_info
pathList = path.split('/')[1:]
controller = state.app.root
resource = state.request.context.get('resource')
if pathList:
for item in pathList:
controller = getattr(controller, item)
if resource == item:
return controller
else:
return controller

View File

@ -110,6 +110,9 @@ class ApiManager(object):
retry -= 1
return ret_val
# TODO(JinLi) This code now is hard-coded to create default interface when
# creating ports. Need to change the hardcoded code to support all types
# of YAML in future.
def create_ports(self, api_class, values):
ret_obj = api_class.create_in_db(values)
#
@ -132,6 +135,7 @@ class ApiManager(object):
name = 'default'
data = {'id': values.get('id'),
'port_id': values.get('id'),
"tenant_id": values.get('tenant_id', ''),
'name': name,
'segmentation_type': 'none',
'segmentation_id': 0}

View File

@ -6,17 +6,28 @@ objects:
type: uuid
primary: true
description: "UUID of Object"
tenant_id:
type: uuid
required: true
description: "UUID of Tenant"
name:
type: string
length: 64
description: "Descriptive name of Object"
policies:
create:
role: "rule:admin_or_owner"
delete:
role: "rule:admin_or_owner"
list:
role: "rule:admin"
get:
role: "rule:admin_or_owner"
update:
role: "rule:admin_or_owner"
BasePort:
extends: BaseObject
attributes:
tenant_id:
type: uuid
required: true
description: "UUID of Tenant owning this Port"
mac_address:
type: string
length: 18
@ -108,6 +119,10 @@ objects:
description: "Description of Service"
BaseServiceBinding:
attributes:
tenant_id:
type: uuid
required: true
description: "UUID of Tenant"
interface_id:
type: uuid
required: true
@ -116,4 +131,15 @@ objects:
service_id:
type: uuid
required: true
description: "Pointer to Service instance"
description: "Pointer to Service instance"
policies:
create:
role: "rule:admin_or_owner"
delete:
role: "rule:admin_or_owner"
list:
role: "rule:admin"
get:
role: "rule:admin_or_owner"
update:
role: "rule:admin_or_owner"

View File

@ -73,6 +73,10 @@ objects:
name: vpnafconfig
plural_name: vpnafconfigs
attributes:
tenant_id:
type: uuid
required: true
description: "UUID of Tenant"
vrf_rt_value:
required: true
type: string
@ -95,6 +99,17 @@ objects:
type: string
length: 32
description: "Route target export policy"
policies:
create:
role: "rule:admin_or_owner"
delete:
role: "rule:admin_or_owner"
get:
role: "rule:admin_or_owner"
list:
role: "rule:admin"
update:
role: "rule:admin_or_owner"
BGPPeering:
api:
name: bgppeering

View File

@ -35,6 +35,7 @@ class MyData(object):
ApiGenData = MyData()
ApiGenData.svc_controllers = {}
API_OBJECT_CLASSES = {}
class ProtonVersion(APIBase):
@ -170,6 +171,8 @@ class APIGenerator(object):
return controller
def create_api(self, root, service_name, db_models):
global API_OBJECT_CLASSES
API_OBJECT_CLASSES[service_name] = {}
self.db_models = db_models
self.service_name = service_name
self.controllers = {}
@ -200,6 +203,11 @@ class APIGenerator(object):
# api_name
api_name = table_data['api']['plural_name']
# Store each api_object_class created for the api.
# As in some situations, policy authorization will need to
# call an api_object_class to prefetch data
API_OBJECT_CLASSES[service_name][api_name] = api_object_class
# primary_key_type
p_type, p_vals, p_fmt = self.get_primary_key_type(table_data)
primary_key_type = self.translate_model_to_api_type(p_type,

View File

@ -0,0 +1,44 @@
# Copyright 2017, AT&T
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log._i18n import _
from oslo_log import log as logging
from oslo_policy import policy as oslo_policy
from gluon.particleGenerator import generator
LOG = logging.getLogger(__name__)
def policy_name(service, resource, action):
re = "%s:%s_%ss"
return re % (service, action, resource.lower())
def generatePolicies(service_list):
policies = []
for service in service_list:
model = generator.load_model_for_service(service)
generator.validate_policies(model)
for obj_name, obj_val in model['api_objects'].items():
actions = obj_val.get('policies')
for action, rule in actions.items():
name = policy_name(service, obj_name, action)
policy = oslo_policy.RuleDefault(name, rule.get('role'))
LOG.info('%(n)s : %(r)s' % dict(n=name, r=rule.get('role')))
policies.append(policy)
return policies

View File

@ -44,6 +44,32 @@ def raise_obj_error(obj_name, format_str, val_tuple):
raise_format_error("Object: %s, %s", (obj_name, str))
# Check if policies are defined for each object and actions are valid.
# Does not verify the content of the policy for undefined rules and cyclic
# rules. The content of policies will be verified by calling the oslo_policy's
# load_rules funtions after all policies are registered.
def validate_policies(model):
'''Each api object should have policies defined'''
allowed_actions = ['create', 'delete', 'get', 'list', 'update']
for obj_name, obj_val in model['api_objects'].items():
if 'policies' not in obj_val:
raise_obj_error(obj_name,
'%s has no policies defined.',
(obj_name))
policies = obj_val.get('policies')
for action, policy in policies.items():
if action not in allowed_actions:
raise_obj_error(obj_name,
'In %s policies, %s is not a valid action.',
(obj_name, action))
for action in allowed_actions:
if action not in policies:
raise_obj_error(obj_name,
'In %s policies, action %s is not defined.',
(obj_name, action))
def validate_attributes(obj_name, obj, model):
props = ['type', 'primary', 'description', 'required',
'length', 'values', 'format', 'min', 'max']

View File

@ -16,10 +16,16 @@
import itertools
from gluon.particleGenerator import generator
from gluon.particleGenerator import PolicyGenerator
from gluon.policies import base
from gluon.policies import net_l3vpn
def list_rules():
service_list = generator.get_service_list()
return itertools.chain(
base.list_rules()
base.list_rules(),
PolicyGenerator.generatePolicies(service_list)
# net_l3vpn.list_rules()
)

View File

@ -16,6 +16,17 @@
from oslo_policy import policy
RULE_CONTEXT_IS_ADMIN = 'rule:context_is_admin'
RULE_OWNER = 'rule:owner'
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
RULE_CONTEXT_IS_ADVSVC = 'rule:context_is_advsvc'
RULE_ADMIN_OR_NETWORK_OWNER = 'rule:admin_or_network_owner'
RULE_ADMIN_OWNER_OR_NETWORK_OWNER = 'rule:admin_owner_or_network_owner'
RULE_ADMIN_ONLY = 'rule:admin_only'
RULE_REGULAR_USER = 'rule:regular_user'
RULE_DEFAULT = 'rule:default'
rules = [
policy.RuleDefault('context_is_admin', 'role:admin'),
policy.RuleDefault('owner', 'tenant_id:%(tenant_id)s'),

214
gluon/policies/net_l3vpn.py Normal file
View File

@ -0,0 +1,214 @@
# Copyright (c) 2016 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from gluon.policies import base
# TODO(JinLi) This file is NOT used, it is an example of moving policy to code.
# Unlike other Openstack projects whose api has a fix set of restControllers,
# Gluon dynamically generates its restControllers from yaml files. If Gluon
# follows the policy in code approach, Gluon users will need to modify source
# code by adding similar files like this one. And then call the list_rules
# function inside the gluon.policies.__init__.py
#
# Gluon takes a different approach by defining policies inside the yaml file of
# a model, so that users do not need to modify any source code
#
# If user prefers to use plicy in code, they can use this file. And create
# similar file for new service.
net_l3vpn_policies = [
policy.RuleDefault(
name='net-l3vpn:create_dataplanetunnels',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_dataplanetunnels',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_dataplanetunnels',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_dataplanetunnels',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_dataplanetunnels',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_bgppeerings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_bgppeerings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_bgppeerings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_bgppeerings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_bgppeerings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_vpnafconfigs',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_vpnafconfigs',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_vpnafconfigs',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_vpnafconfigs',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_vpnafconfigs',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_vpnservices',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_vpnservices',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_vpnservices',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_vpnservices',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_vpnservices',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_interfaces',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_interfaces',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_interfaces',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_interfaces',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_interfaces',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_vpnbindings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_vpnbindings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_vpnbindings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_vpnbindings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_vpnbindings',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:create_ports',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_ports',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:update_ports',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:get_one_ports',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
),
policy.RuleDefault(
name='net-l3vpn:delete_ports',
check_str=base.RULE_ADMIN_OR_OWNER,
description='net-l3vpn policy'
)
]
def list_rules():
return net_l3vpn_policies