安全策略
📄字数 9.4K
👁️阅读量 加载中...
XuguDB 通过安全策略实现细粒度的行级访问控制,限制用户对表中各行数据的读取与修改,防止越权操作和数据泄露,满足安全合规要求。需要注意的是,安全策略仅用于进一步限制用户的访问权限,并不能替代数据库原有的权限控制机制:用户在访问数据前,必须已具备对目标表的相应操作权限。
安全策略采用基于标记的访问控制思想。每个安全策略由预定义的一组安全标记规则组成。用户会被分配安全标记,表也会通过安全标记列为每行数据记录安全标记。进行访问控制判断时,会将用户的安全标记与数据行的安全标记进行比较,决定是否允许读取或修改该行数据,从而实现强制访问控制(简称 MAC
)。
安全策略定义包含两类标记组件:安全等级和安全范畴,分别用于访问权限控制的等级和范畴两个维度。
为了便于集中管理和运维,XuguDB 提供了完善的安全策略管理功能:
- 策略定义管理:创建新的安全策略,修改或删除已有策略。安全策略的定义支持后续调整,例如重命名策略,增加或删除其中的安全标记组件。
- 标记分配管理:给用户和表分配、修改或移除安全标记,灵活调整访问控制规则。
- 给用户添加安全策略时,实际上是给用户分配安全标记,记录在系统表
SYS_USERS
的相应字段中(详见相关系统表)。 - 给表添加安全策略时,不仅会把安全标记记录到系统表
SYS_TABLES
中,还会在表结构上新增一个安全标记列,用来控制每行数据的访问权限。
- 给用户添加安全策略时,实际上是给用户分配安全标记,记录在系统表
- 权限与角色控制:安全策略的管理操作由数据库安全管理员(
SYSSSO
)或具有安全管理员权限的用户执行,确保管理权限受到严格限制。
为避免配置过于复杂带来的管理负担,安全策略的设计应合理规划。虽然数据库系统对安全策略的数量没有强制限制,但建议不要创建过多策略。同时,每个安全策略中安全范畴的数量也有上限(最多48个)。
此外,XuguDB 还提供了参数来灵活控制安全策略的执行行为,便于按需调整访问控制策略(详见相关参数)。
通过以上机制,安全策略为系统安全管理提供了强有力的工具,能够在敏感数据隔离、分级保护等场景下,实现灵活、可控且可审计的访问控制策略。
一、安全标记
安全标记是安全策略用于控制用户访问表中行数据时分配的访问控制标识,描述了访问权限的等级和范围。一个安全标记由安全等级和安全范畴两部分组成,总长度为64位,其中16位用于安全等级,48位用于安全范畴。
注意
插入操作不受安全等级和安全范畴的访问控制限制。
1.1 安全等级
安全等级用于对访问权限进行分级控制,创建时需指定名称和整数安全等级值(例如:ADD LEVEL level_1 AS 1
),取值范围为 0 至 30000,包含边界值。
- 读访问控制:用户的安全等级必须大于或等于数据行的安全等级,才能读取该行数据。
- 写访问控制(更新与删除):用户的安全等级必须等于数据行的安全等级,才能更新或删除该行数据。
通过安全等级的分级比较,系统能够实现强制的分级访问控制策略,满足保密性和最小权限原则。
1.2 安全范畴
安全范畴用于定义访问控制的类别或范围,创建时只需指定名称(例如:ADD CATEGORY category_1
),具体类别值由系统自动分配。每个安全策略最多可定义48个安全范畴。
安全范畴是集合类型,不存在等级高低,而是通过集合包含关系进行比较:
- 读访问控制:用户的安全范畴集合必须包含数据行的所有安全范畴,才能读取该行数据。
- 写访问控制(更新与删除):用户的安全范畴集合必须和数据行的安全范畴集合完全相同,才能更新或删除该行数据。
这种基于集合关系的设计,可以支持跨部门、项目组或其他业务域的灵活隔离控制,实现更细粒度的安全策略。
二、安全策略管理
安全策略管理包括策略的创建、修改和删除,以及为用户和表分配、调整或移除安全标记。这些操作需由数据库安全管理员(SYSSSO
)或具有安全管理员权限的用户执行,确保访问控制策略能够安全、灵活地满足业务和合规要求。
2.1 创建安全策略
语法格式:
参数说明:
policy_name
:安全策略名称。level_name
:安全等级名称。level_value
:安全等级的值,取值范围为 0 至 30000,包含边界值。category_name
:安全范畴名称。
示例:
创建不带等级和范畴的安全策略。
sqlSQL> CREATE POLICY policy_1;
1创建带等级的安全策略。
sqlSQL> CREATE POLICY policy_2 ADD LEVEL level_1 AS 1;
1创建带范畴的安全策略。
sqlSQL> CREATE POLICY policy_3 ADD CATEGORY category_1;
1创建带等级和范畴的安全策略。
sqlSQL> CREATE POLICY policy_4 ADD LEVEL level_1 AS 1,ADD LEVEL level_2 AS 2,ADD CATEGORY category_1,ADD CATEGORY category_2;
1
2.2 修改安全策略
语法格式:
参数说明:
policy_name
:安全策略名称。new_policy_name
:新的安全策略名称。level_name
:安全等级名称。level_value
:安全等级的值,取值为非负整数。category_name
:安全范畴名称。new_level_name
:新的安全等级名称。new_category_name
:新的安全范畴名称。
示例:
更改安全策略名称。
sqlSQL> ALTER POLICY policy_1 RENAME TO policy_001;
1更改安全等级名称。
sqlSQL> ALTER POLICY policy_2 ALTER LEVEL level_1 RENAME TO level_2;
1删除安全范畴。
sqlSQL> ALTER POLICY policy_3 DROP CATEGORY category_1;
1增加多个安全等级的同时删除一个安全范畴。
sqlSQL> ALTER POLICY policy_4 ADD LEVEL level_3 AS 3, ADD LEVEL level_4 AS 4,DROP LEVEL level_1,DROP CATEGORY category_1;
1
2.3 删除安全策略
语法格式:
参数说明:
policy_name
:安全策略名称。
示例:
sql
SQL> DROP POLICY policy_4;
1
2.4 管理用户安全策略
语法格式:
重要
- 每个用户只能分配一个安全策略。
ADD
和ALTER
只是语法上有所区别,功能上都用于修改用户的安全策略。 - 分配安全策略时,会为用户生成并记录安全标记(存储在系统表
SYS_USERS
的相应字段中)。如果未指定安全等级或安全范畴,将使用原有值(未设置时默认为0)。
参数说明:
user_name
:用户名称。policy_name
:安全策略名称。level_name
:安全等级名称。category_name
:安全范畴名称。
示例:
给用户分配特定等级和范畴的安全策略。
sql-- SYSDBA 用户登录,创建用户 SQL> CREATE USER usr_1 IDENTIFIED BY '123QWE$$&'; -- SYSSSO 用户登录,给用户分配安全策略 SQL> ALTER USER POLICY usr_1 ADD policy_4 LEVEL level_3 CATEGORY category_2;
1
2
3
4
5修改用户的安全策略:更改安全等级。
sqlSQL> ALTER USER POLICY usr_1 ALTER policy_4 LEVEL level_4;
1删除用户的安全策略。
sqlSQL> ALTER USER POLICY usr_1 DROP policy_4;
1
2.5 管理表安全策略
语法格式:
重要
- 每个表只能添加一个安全策略。要修改表上已有的安全策略(不包括修改安全标记列的可见性),只能先删除安全策略,然后再添加。
- 添加安全策略时,不仅会在系统表
SYS_TABLES
中记录表的安全标记,还会在表结构中新增安全标记列,用于行级访问控制。 - 删除安全策略后,表中原有的安全标记列将被保留,但读写操作将不再受其控制。有关删除后重新添加安全策略时的行为,详见2.6 综合示例。
参数说明:
schema_name
:表所属模式的名称。table_name
:表的名称。policy_name
:安全策略名称。label_name
:安全标记列的名称,不能和目标表已有的列同名。label_value
:安全标记值。格式为'安全等级名称:安全范畴名称1,安全范畴名称2,...'
,必须用单引号包裹,安全范畴部分为可选。每个标记值只能指定一个安全等级。opt_hide
:- 省略参数时:等同于
NOT HIDE
。 NOT HIDE
:不隐藏安全标记列。HIDE
:隐藏安全标记列。
- 省略参数时:等同于
示例:
给表添加安全策略。
sql-- usr_1 用户登录,创建表 SQL> CREATE TABLE tab_test (c1 INT,c2 VARCHAR); -- SYSSSO 用户登录,给表添加安全策略 SQL> ALTER TABLE POLICY usr_1.tab_test ADD policy_4 COLUMN c3 NOT HIDE LABEL 'level_3:category_2';
1
2
3
4
5修改表的安全策略:将安全标记列设置为不可见。
sqlSQL> ALTER TABLE POLICY usr_1.tab_test ALTER policy_4 HIDE;
1删除表的安全策略。
sqlSQL> ALTER TABLE POLICY usr_1.tab_test DROP policy_4;
1
2.6 综合示例
本节通过一个完整示例,演示安全策略的创建、管理以及用户和表上安全标记分配的操作流程。
提示
安全范畴的值是系统自动分配的,可以通过系统表 SYS_CATEGORIES
查询,详情见相关系统表。
sql
/* SYSDBA 用户登录 */
-- 创建用户
SQL> CREATE USER usr_1 IDENTIFIED BY '123QWE$$&';
-- 创建表并插入数据
SQL> CREATE TABLE usr_1.tab_test_1(c1 INT,c2 VARCHAR);
SQL> INSERT INTO usr_1.tab_test_1 VALUES (1,'a'),(2,'b');
/* SYSSSO 用户登录 */
-- 创建安全策略
SQL> CREATE POLICY policy_1 ADD LEVEL level_1 AS 1,ADD LEVEL level_2 AS 2,ADD LEVEL level_3 AS 3,
ADD CATEGORY category_1,ADD CATEGORY category_2;
-- 给用户分配安全策略:安全等级3,范畴为 category_1 和 category_2
SQL> ALTER USER POLICY usr_1 ADD policy_1 LEVEL level_3 CATEGORY category_1,category_2;
-- 给表分配安全策略:安全等级2,范畴为 category_1 和 category_2
SQL> ALTER TABLE POLICY usr_1.tab_test_1 ADD policy_1 COLUMN c3 NOT HIDE LABEL 'level_2:category_1,category_2';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注意
- 每个安全标记由一个安全等级和一个或多个安全范畴组成,总共64位:
- 高16位是安全等级。
- 低48位是安全范畴位图,每个比特位对应一个安全范畴。例如下面示例中安全范畴
category_1
和category_2
的值分别对应位图中的第0位和第1位,即十六进制0x1
和0x2
。
- 给表添加安全策略时,会在表结构中新增一个安全标记列。该列的可见性可以用
NOT HIDE
和HIDE
控制。表中每行数据的读写权限由该安全标记列的值来控制,具体见安全标记。
sql
/* usr_1 用户登录 */
-- 查询表数据:行的安全标记值是 562949953421315 = (2 << 48) | (category_1 | category_2)
SQL> SELECT * FROM tab_test_1;
+----+----+-----------------+
| C1 | C2 | C3 |
+----+----+-----------------+
| 1 | a | 562949953421315 |
| 2 | b | 562949953421315 |
+----+----+-----------------+
-- 尝试更新数据(失败):用户的安全等级(3)不等于数据行的安全等级(2)
SQL> UPDATE tab_test_1 SET c2='c' WHERE c1=2;
Error: [E18028] 更改操作违反强制安全控制策略
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
注意
表中存在安全标记列时:
- 用户插入新行,新行的安全标记值来源于用户当前的安全等级和范畴(未设置时默认为0)。
- 用户修改已有数据行,行的安全标记值不会改变。
sql
/* usr_1 用户登录 */
-- 插入新数据
SQL> INSERT INTO tab_test_1 VALUES (3,'cc');
-- 查询表数据:新行的安全标记值是 844424930131971 = (3 << 48) | (category_1 | category_2)
SQL> SELECT * FROM tab_test_1;
+----+----+-----------------+
| C1 | C2 | C3 |
+----+----+-----------------+
| 1 | a | 562949953421315 |
| 2 | b | 562949953421315 |
| 3 | cc | 844424930131971 |
+----+----+-----------------+
-- 更新自己插入的行(成功)
SQL> UPDATE tab_test_1 SET c2='c' WHERE c1=3;
/* SYSSSO 用户登录 */
-- 修改用户安全等级为 level_2
SQL> ALTER USER POLICY usr_1 ALTER policy_1 LEVEL level_2;
/* usr_1 用户登录 */
-- 更新原来的行(成功)
SQL> UPDATE tab_test_1 SET c2='c' WHERE c1=2;
/* SYSSSO 用户登录 */
-- 还原用户安全等级
SQL> ALTER USER POLICY usr_1 ALTER policy_1 LEVEL level_3;
/* usr_1 用户登录 */
-- 查询表数据:第二行数据的安全标记值没有改变
SQL> SELECT * FROM tab_test_1;
+----+----+-----------------+
| C1 | C2 | C3 |
+----+----+-----------------+
| 1 | a | 562949953421315 |
| 2 | c | 562949953421315 |
| 3 | c | 844424930131971 |
+----+----+-----------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
注意
删除表的安全策略后,表的读写将不再受安全标记控制。
sql
/* SYSDBA 用户登录 */
-- 查询数据
SQL> SELECT * FROM usr_1.tab_test_1;
+----+----+
| C1 | C2 |
+----+----+
+----+----+
/* SYSSSO 用户登录 */
-- 删除表的安全策略
SQL> ALTER TABLE POLICY usr_1.tab_test_1 DROP policy_1;
/* SYSDBA 用户登录 */
-- 不受安全策略限制,更新数据成功
SQL> UPDATE usr_1.tab_test_1 SET c2='b' WHERE c1=2;
SQL> SELECT * FROM usr_1.tab_test_1;
+----+----+
| C1 | C2 |
+----+----+
| 1 | a |
| 2 | b |
| 3 | c |
+----+----+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意
重新给表添加安全策略时:
- 如果新策略的安全标记列名称和之前相同,并且系统表
SYS_COLUMNS
中仍保留该列信息,则原有的安全标记值会保留。 - 如果新策略的列名不同,所有值都会替换为表的安全标记值。
sql
/* SYSDBA 用户登录 */
-- 插入新数据:如果查询系统表 SYS_COLUMNS,会发现安全标记列 C3 存在,默认值是562949953421315,因此这里会给安全标记列插入非空值
SQL> INSERT INTO usr_1.tab_test_1 VALUES (4,'d');
/* SYSSSO 用户登录 */
-- 使用原来的安全标记列名重新添加安全策略
SQL> ALTER TABLE POLICY usr_1.tab_test_1 ADD policy_1 COLUMN c3 NOT HIDE LABEL 'level_1:category_1,category_2';
/* usr_1 用户登录 */
-- 查询表数据
SQL> SELECT * FROM tab_test_1;
+----+----+-----------------+
| C1 | C2 | C3 |
+----+----+-----------------+
| 1 | a | 562949953421315 |
| 2 | b | 562949953421315 |
| 3 | c | 844424930131971 |
| 4 | d | 562949953421315 |
+----+----+-----------------+
/* SYSSSO 用户登录 */
-- 删除表的安全策略
SQL> ALTER TABLE POLICY usr_1.tab_test_1 DROP policy_1;
-- 重新添加安全策略,使用不同的安全标记列名
SQL> ALTER TABLE POLICY usr_1.tab_test_1 ADD policy_1 COLUMN c4 NOT HIDE LABEL 'level_3:category_1,category_2';
/* usr_1 用户登录 */
-- 查询表数据:都替换成表的安全标记值
SQL> SELECT * FROM tab_test_1;
+----+----+-----------------+
| C1 | C2 | C4 |
+----+----+-----------------+
| 1 | a | 844424930131971 |
| 2 | b | 844424930131971 |
| 3 | c | 844424930131971 |
| 4 | d | 844424930131971 |
+----+----+-----------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
三、相关系统表
XuguDB 通过以下系统表来管理安全策略的元数据,以及为用户和表分配的安全标记信息。字段的详细定义参考系统表文档。
系统表 | 作用 |
---|---|
SYS_POLICIES | 记录安全策略的基本信息(策略 ID、名称等) |
SYS_LEVELS | 记录各安全策略中定义的安全等级信息 |
SYS_CATEGORIES | 记录各安全策略中定义的安全范畴信息 |
SYS_USERS | 记录用户分配的安全策略及其安全标记(等级、范畴) |
SYS_TABLES | 记录表分配的安全策略及其安全标记列信息 |
下面的示例演示了安全策略创建、分配以及系统表中记录的变化。
提示
XLS_PID
表示分配的安全策略 ID。XLS_LID
表示安全等级 ID。XLS_CIDS
是安全范畴的位图表示。- 表的
XLS_COL_NO
表示安全标记列在表结构中的位置(列号),XLS_COL_OPT
表示安全标记列的可见性选项。
sql
/* SYSDBA 用户登录 */
-- 创建用户和表
SQL> CREATE USER usr_1 IDENTIFIED BY '123QWE$$&';
SQL> CREATE TABLE usr_1.tab_test_1(c1 INT,c2 VARCHAR);
-- 查询用户和表的初始安全标记信息
SQL> SELECT xls_pid,xls_lid,xls_cids FROM SYS_USERS WHERE user_name='USR_1';
+---------+---------+----------+
| XLS_PID | XLS_LID | XLS_CIDS |
+---------+---------+----------+
| <NULL> | <NULL> | <NULL> |
+---------+---------+----------+
SQL> SELECT xls_pid,xls_col_no,xls_col_opt FROM SYS_TABLES WHERE table_name='TAB_TEST_1';
+---------+------------+-------------+
| XLS_PID | XLS_COL_NO | XLS_COL_OPT |
+---------+------------+-------------+
| 0 | <NULL> | <NULL> |
+---------+------------+-------------+
/* SYSSSO 用户登录 */
-- 创建安全策略
SQL> CREATE POLICY policy_1 ADD LEVEL level_1 AS 1,ADD LEVEL level_2 AS 2,ADD LEVEL level_3 AS 3,
ADD CATEGORY category_1,ADD CATEGORY category_2;
-- 给用户分配安全策略
SQL> ALTER USER POLICY usr_1 ADD policy_1 LEVEL level_3 CATEGORY category_1,category_2;
-- 给表分配安全策略
SQL> ALTER TABLE POLICY usr_1.tab_test_1 ADD policy_1 COLUMN c3 NOT HIDE LABEL 'level_2:category_1,category_2';
/* SYSDBA 用户登录 */
-- 查看安全策略信息
SQL> SELECT policy_id,policy_name FROM SYS_POLICIES;
+-----------+-------------+
| POLICY_ID | POLICY_NAME |
+-----------+-------------+
| 1048578 | POLICY_1 |
+-----------+-------------+
-- 查看安全等级信息
SQL> SELECT pid,lid,name FROM SYS_LEVELS;
+---------+-----+---------+
| PID | LID | NAME |
+---------+-----+---------+
| 1048578 | 1 | LEVEL_1 |
| 1048578 | 2 | LEVEL_2 |
| 1048578 | 3 | LEVEL_3 |
+---------+-----+---------+
-- 查看安全范畴信息
SQL> SELECT pid,cid,name FROM SYS_CATEGORIES;
+---------+-----+------------+
| PID | CID | NAME |
+---------+-----+------------+
| 1048578 | 0 | CATEGORY_1 |
| 1048578 | 1 | CATEGORY_2 |
+---------+-----+------------+
-- 查询用户和表的安全标记信息
SQL> SELECT xls_pid,xls_lid,xls_cids FROM SYS_USERS WHERE user_name='USR_1';
+---------+---------+----------+
| XLS_PID | XLS_LID | XLS_CIDS |
+---------+---------+----------+
| 1048578 | 3 | 3 |
+---------+---------+----------+
SQL> SELECT xls_pid,xls_col_no,xls_col_opt FROM SYS_TABLES WHERE table_name='TAB_TEST_1';
+---------+------------+-------------+
| XLS_PID | XLS_COL_NO | XLS_COL_OPT |
+---------+------------+-------------+
| 1048578 | 2 | 0 |
+---------+------------+-------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
四、相关参数
4.1 filter_policy
filter_policy
是一个会话级参数,用于控制当前会话对安全策略(强制访问控制)违规行为的处理方式。默认值为 0
。不同取值的含义如下:
0
:对所有普通查询隐藏安全标记不足的数据行(即用户安全标记不足以访问数据行时,该行数据不会返回)。对于统计函数则可以正常返回聚合结果,不报错。1
:对所有操作隐藏安全标记不足的数据行,包括统计聚合。2
:对所有非统计聚合查询,当出现安全策略违规(用户安全标记不足以访问数据行)时直接报错,禁止查询。3
:对所有操作严格执行强制访问控制,任何安全策略违规都会报错。
下面示例展示了在不同 filter_policy
设置下,查询带有安全标记的表时的行为差异。
sql
/* SYSDBA 用户登录 */
-- 创建用户和表
SQL> CREATE USER usr_1 IDENTIFIED BY '123QWE$$&';
SQL> CREATE TABLE usr_1.tab_test_1(c1 INT,c2 VARCHAR);
SQL> INSERT INTO usr_1.tab_test_1 VALUES (1,'a'),(2,'b');
/* SYSSSO 用户登录 */
-- 创建安全策略
SQL> CREATE POLICY policy_1 ADD LEVEL level_1 AS 1,ADD LEVEL level_2 AS 2,ADD LEVEL level_3 AS 3,
ADD CATEGORY category_1,ADD CATEGORY category_2;
-- 给表分配安全策略
SQL> ALTER TABLE POLICY usr_1.tab_test_1 ADD policy_1 COLUMN c3 NOT HIDE LABEL 'level_2:category_1,category_2';
/* SYSDBA 用户登录 */
-- filter_policy 为0
SQL> SHOW filter_policy;
+---------------+
| FILTER_POLICY |
+---------------+
| 0 |
+---------------+
SQL> SELECT * FROM usr_1.tab_test_1;
+----+----+----+
| C1 | C2 | C3 |
+----+----+----+
+----+----+----+
SQL> SELECT sum(c1) c1_sum FROM usr_1.tab_test_1;
+--------+
| C1_SUM |
+--------+
| 3 |
+--------+
-- filter_policy 为3
SQL> SET filter_policy TO 3;
SQL> SHOW filter_policy;
+---------------+
| FILTER_POLICY |
+---------------+
| 3 |
+---------------+
SQL> SELECT * FROM usr_1.tab_test_1;
Error: [E18030] 违反强制访问策略
SQL> SELECT sum(c1) c1_sum FROM usr_1.tab_test_1;
Error: [E18030] 违反强制访问策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46