Add RBAC enforcement to pools v2 API

This patch adds policies and enforcement to the Octavia v2 API for pools.

It also fixes a minor issue with the specs tox job.

Change-Id: Id2aa4dfad149583f9cb16205cb617f6e2a1bc92e
Partial-Bug: #1690481
This commit is contained in:
Michael Johnson 2017-06-20 09:36:12 -07:00
parent 0ce46fe8d0
commit 8987ab39ed
6 changed files with 502 additions and 9 deletions

View File

@ -49,6 +49,13 @@ class PoolsController(base.BaseController):
"""Gets a pool's details."""
context = pecan.request.context.get('octavia_context')
db_pool = self._get_db_pool(context.session, id)
# Check that the user is authorized to show this pool
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='get_one')
target = {'project_id': db_pool.project_id}
context.policy.authorize(action, target)
result = self._convert_db_to_type(db_pool, pool_types.PoolResponse)
return pool_types.PoolRootResponse(pool=result)
@ -58,17 +65,31 @@ class PoolsController(base.BaseController):
"""Lists all pools."""
pcontext = pecan.request.context
context = pcontext.get('octavia_context')
if context.is_admin or CONF.auth_strategy == constants.NOAUTH:
if project_id:
project_id = {'project_id': project_id}
else:
project_id = {}
# Check that the user is authorized to list pools under all projects
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='get_all-global')
target = {'project_id': project_id}
if not context.policy.authorize(action, target, do_raise=False):
# Not a global observer or admin
if project_id is None:
project_id = context.project_id
# Check that the user is authorized to list lbs under this project
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='get_all')
target = {'project_id': project_id}
context.policy.authorize(action, target)
if project_id is None:
query_filter = {}
else:
project_id = {'project_id': context.project_id}
query_filter = {'project_id': project_id}
db_pools, links = self.repositories.pool.get_all(
context.session, show_deleted=False,
pagination_helper=pcontext.get(constants.PAGINATION_HELPER),
**project_id)
**query_filter)
result = self._convert_db_to_type(db_pools, [pool_types.PoolResponse])
return pool_types.PoolsRootResponse(pools=result, pools_links=links)
@ -166,6 +187,12 @@ class PoolsController(base.BaseController):
"loadbalancer_id, listener_id")
raise exceptions.ValidationException(detail=msg)
# Check that the user is authorized to create under this project
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='post')
target = {'project_id': pool.project_id}
context.policy.authorize(action, target)
lock_session = db_api.get_session(autocommit=False)
if self.repositories.check_quota_met(
context.session,
@ -245,6 +272,13 @@ class PoolsController(base.BaseController):
pool = pool_.pool
context = pecan.request.context.get('octavia_context')
db_pool = self._get_db_pool(context.session, id)
# Check that the user is authorized to update this pool
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='put')
target = {'project_id': db_pool.project_id}
context.policy.authorize(action, target)
self._test_lb_and_listener_statuses(
context.session, lb_id=db_pool.load_balancer_id,
listener_ids=self._get_affected_listener_ids(db_pool))
@ -276,6 +310,13 @@ class PoolsController(base.BaseController):
if len(db_pool.l7policies) > 0:
raise exceptions.PoolInUseByL7Policy(
id=db_pool.id, l7policy_id=db_pool.l7policies[0].id)
# Check that the user is authorized to delete this pool
action = '{rbac_obj}{action}'.format(
rbac_obj=constants.RBAC_POOL, action='delete')
target = {'project_id': db_pool.project_id}
context.policy.authorize(action, target)
self._test_lb_and_listener_statuses(
context.session, lb_id=db_pool.load_balancer_id,
listener_ids=self._get_affected_listener_ids(db_pool))

View File

@ -430,3 +430,4 @@ RULE_API_WRITE = 'rule:load-balancer:write'
RULE_ANY = '@'
RBAC_LOADBALANCER = '{}:loadbalancer:'.format(LOADBALANCER_API)
RBAC_LISTENER = '{}:listener:'.format(LOADBALANCER_API)
RBAC_POOL = '{}:pool:'.format(LOADBALANCER_API)

View File

@ -16,6 +16,7 @@ import itertools
from octavia.policies import base
from octavia.policies import listener
from octavia.policies import loadbalancer
from octavia.policies import pool
def list_rules():
@ -23,4 +24,5 @@ def list_rules():
base.list_rules(),
loadbalancer.list_rules(),
listener.list_rules(),
pool.list_rules(),
)

67
octavia/policies/pool.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright 2017 Rackspace, US Inc.
# 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 octavia.common import constants
from oslo_policy import policy
rules = [
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='get_all'),
constants.RULE_API_READ,
"List Pools",
[{'method': 'GET', 'path': '/v2.0/lbaas/pools'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='get_all-global'),
constants.RULE_API_READ_GLOBAL,
"List Pools including resources owned by others",
[{'method': 'GET', 'path': '/v2.0/lbaas/pools'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='post'),
constants.RULE_API_WRITE,
"Create a Pool",
[{'method': 'POST', 'path': '/v2.0/lbaas/pools'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='get_one'),
constants.RULE_API_READ,
"Show Pool details",
[{'method': 'GET',
'path': '/v2.0/lbaas/pools/{pool_id}'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='put'),
constants.RULE_API_WRITE,
"Update a Pool",
[{'method': 'PUT',
'path': '/v2.0/lbaas/pools/{pool_id}'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL,
action='delete'),
constants.RULE_API_WRITE,
"Remove a Pool",
[{'method': 'DELETE',
'path': '/v2.0/lbaas/pools/{pool_id}'}]
),
]
def list_rules():
return rules

View File

@ -14,6 +14,8 @@
import mock
from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
from octavia.common import constants
@ -36,6 +38,7 @@ class TestPool(base.BaseAPITest):
self.lb = self.create_load_balancer(
uuidutils.generate_uuid()).get('loadbalancer')
self.lb_id = self.lb.get('id')
self.project_id = self.lb.get('project_id')
self.set_lb_status(self.lb_id)
@ -62,6 +65,67 @@ class TestPool(base.BaseAPITest):
response.pop('updated_at')
self.assertEqual(api_pool, response)
def test_get_authorized(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_pool['provisioning_status'] = constants.ACTIVE
api_pool['operating_status'] = constants.ONLINE
api_pool.pop('updated_at')
self.set_lb_status(lb_id=self.lb_id)
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.project_id):
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_member'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
response = self.get(self.POOL_PATH.format(
pool_id=api_pool.get('id'))).json.get(self.root_tag)
response.pop('updated_at')
self.assertEqual(api_pool, response)
self.conf.config(auth_strategy=auth_strategy)
def test_get_not_authorized(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_pool['provisioning_status'] = constants.ACTIVE
api_pool['operating_status'] = constants.ONLINE
api_pool.pop('updated_at')
self.set_lb_status(lb_id=self.lb_id)
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
response = self.get(self.POOL_PATH.format(
pool_id=api_pool.get('id')), status=401)
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json)
def test_get_hides_deleted(self):
api_pool = self.create_pool(
self.lb_id,
@ -145,7 +209,23 @@ class TestPool(base.BaseAPITest):
self.conf.config(auth_strategy=constants.KEYSTONE)
with mock.patch.object(octavia.common.context.Context, 'project_id',
pool3['project_id']):
pools = self.get(self.POOLS_PATH).json.get(self.root_tag_list)
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_member'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
pools = self.get(self.POOLS_PATH).json.get(self.root_tag_list)
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(1, len(pools))
@ -153,6 +233,85 @@ class TestPool(base.BaseAPITest):
self.assertIn((pool3.get('id'), pool3.get('protocol')),
pool_id_protocols)
def test_get_all_non_admin_global_observer(self):
project_id = uuidutils.generate_uuid()
lb1 = self.create_load_balancer(uuidutils.generate_uuid(), name='lb1',
project_id=project_id)
lb1_id = lb1.get('loadbalancer').get('id')
self.set_lb_status(lb1_id)
pool1 = self.create_pool(
lb1_id, constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
pool2 = self.create_pool(
lb1_id, constants.PROTOCOL_HTTPS,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
pool3 = self.create_pool(
lb1_id, constants.PROTOCOL_TCP,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.project_id):
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_global_observer'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
pools = self.get(self.POOLS_PATH).json.get(self.root_tag_list)
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(3, len(pools))
pool_id_protocols = [(p.get('id'), p.get('protocol')) for p in pools]
self.assertIn((pool1.get('id'), pool1.get('protocol')),
pool_id_protocols)
self.assertIn((pool2.get('id'), pool2.get('protocol')),
pool_id_protocols)
self.assertIn((pool3.get('id'), pool3.get('protocol')),
pool_id_protocols)
def test_get_all_not_authorized(self):
project_id = uuidutils.generate_uuid()
lb1 = self.create_load_balancer(uuidutils.generate_uuid(), name='lb1',
project_id=project_id)
lb1_id = lb1.get('loadbalancer').get('id')
self.set_lb_status(lb1_id)
self.create_pool(
lb1_id, constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
self.create_pool(
lb1_id, constants.PROTOCOL_HTTPS,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
self.create_pool(
lb1_id, constants.PROTOCOL_TCP,
constants.LB_ALGORITHM_ROUND_ROBIN).get(self.root_tag)
self.set_lb_status(lb1_id)
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
pools = self.get(self.POOLS_PATH, status=401).json
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, pools)
def test_get_by_project_id(self):
project1_id = uuidutils.generate_uuid()
project2_id = uuidutils.generate_uuid()
@ -319,6 +478,71 @@ class TestPool(base.BaseAPITest):
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'))
def test_create_authorized(self):
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.project_id):
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_member'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
self.conf.config(auth_strategy=auth_strategy)
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_CREATE,
pool_op_status=constants.OFFLINE)
self.set_lb_status(self.lb_id)
self.assertEqual(constants.PROTOCOL_HTTP, api_pool.get('protocol'))
self.assertEqual(constants.LB_ALGORITHM_ROUND_ROBIN,
api_pool.get('lb_algorithm'))
self.assertIsNotNone(api_pool.get('created_at'))
self.assertIsNone(api_pool.get('updated_at'))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'))
def test_create_not_authorized(self):
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id, status=401)
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, api_pool)
def test_create_with_proxy_protocol(self):
api_pool = self.create_pool(
self.lb_id,
@ -488,6 +712,81 @@ class TestPool(base.BaseAPITest):
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=response.get('id'))
def test_update_authorized(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
new_pool = {'name': 'new_name'}
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.project_id):
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_member'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')),
self._build_body(new_pool))
self.conf.config(auth_strategy=auth_strategy)
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_UPDATE)
self.set_lb_status(self.lb_id)
response = self.get(self.POOL_PATH.format(
pool_id=api_pool.get('id'))).json.get(self.root_tag)
self.assertNotEqual('new_name', response.get('name'))
self.assertIsNotNone(response.get('created_at'))
self.assertIsNotNone(response.get('updated_at'))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=response.get('id'))
def test_update_not_authorized(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
new_pool = {'name': 'new_name'}
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
api_pool = self.put(
self.POOL_PATH.format(pool_id=api_pool.get('id')),
self._build_body(new_pool), status=401)
self.conf.config(auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, api_pool.json)
self.assert_correct_lb_status(self.lb_id, constants.ONLINE,
constants.ACTIVE)
def test_bad_update(self):
api_pool = self.create_pool(
self.lb_id,
@ -543,6 +842,87 @@ class TestPool(base.BaseAPITest):
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_DELETE)
def test_delete_authorize(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_pool['provisioning_status'] = constants.ACTIVE
api_pool['operating_status'] = constants.ONLINE
api_pool.pop('updated_at')
response = self.get(self.POOL_PATH.format(
pool_id=api_pool.get('id'))).json.get(self.root_tag)
response.pop('updated_at')
self.assertEqual(api_pool, response)
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.project_id):
override_credentials = {
'service_user_id': None,
'user_domain_id': None,
'is_admin_project': True,
'service_project_domain_id': None,
'service_project_id': None,
'roles': ['load-balancer_member'],
'user_id': None,
'is_admin': False,
'service_user_domain_id': None,
'project_domain_id': None,
'service_roles': [],
'project_id': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
self.delete(self.POOL_PATH.format(pool_id=api_pool.get('id')))
self.conf.config(auth_strategy=auth_strategy)
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
lb_prov_status=constants.PENDING_UPDATE,
listener_prov_status=constants.PENDING_UPDATE,
pool_prov_status=constants.PENDING_DELETE)
def test_delete_not_authorize(self):
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_pool['provisioning_status'] = constants.ACTIVE
api_pool['operating_status'] = constants.ONLINE
api_pool.pop('updated_at')
response = self.get(self.POOL_PATH.format(
pool_id=api_pool.get('id'))).json.get(self.root_tag)
response.pop('updated_at')
self.assertEqual(api_pool, response)
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
auth_strategy = self.conf.conf.get('auth_strategy')
self.conf.config(auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
self.delete(self.POOL_PATH.format(pool_id=api_pool.get('id')),
status=401)
self.conf.config(auth_strategy=auth_strategy)
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
lb_prov_status=constants.ACTIVE,
listener_prov_status=constants.ACTIVE,
pool_prov_status=constants.ACTIVE)
def test_bad_delete(self):
self.delete(self.POOL_PATH.format(
pool_id=uuidutils.generate_uuid()), status=404)

View File

@ -99,7 +99,9 @@ commands =
--output-file etc/octavia/json.policy.sample
[testenv:specs]
whitelist_externals = rm
whitelist_externals =
rm
find
commands =
find . -type f -name "*.pyc" -delete
rm -f .testrepository/times.dbm