On 11/8/24 12:43 PM, Dumitru Ceara wrote:
> On 11/8/24 10:14, Aleksandr Smirnov (K2 Cloud) wrote:
>>> On 10/4/24 13:41, Aleksandr Smirnov wrote:
>>>> From: Aleksandr Smirnov <[email protected]>
>>>>
>>>> Northd creates hardcoded RBAC role 'ovn-controller' with number of
>>>> predefined permissions. Then it watches for alternations of the role
>>>> and permissions and recover them if they were changed.
>>>> An original code have issues that prevents an user to create any other
>>>> roles.
>>>> Also, builtin permissions are not fully protected against modifications.
>>>>
>>>> The fix reworks this code to recover builtin permissions properly after
>>>> all possible modification scenarios. Also, creation of custom roles and
>>>> permissions becomes available.
>>>>
>>>> Signed-off-by: Aleksandr Smirnov <[email protected]>
>>>> Tested-by: Aleksandr Gnatyuk <[email protected]>
>>>> ---
>>> Hi Aleksandr,
>>>
>>> Sorry for the delay in reviewing this patch. Please see some comments
>>> below.
>>>
>>> Also cc-ing Han, Numan and Mark as RBAC is very important and I want to make
>>> sure we don't miss anything.
>> Hi Dumitru,
>>
> Hi Aleksandr,
Hi Dumitru,
Should I take a break and wait until other guys looked into review or I
need to adopt your comments right now and relaunch review?
>
>> Thank you for your review!
>>
>> Generally I always agree with minor comments while they are really working.
>>
>> For big comments see my answers below.
>>
>>
>>>> northd/ovn-northd.c | 120 ++++++++++++++++++++++++++++++--------------
>>>> tests/ovn-northd.at | 80 +++++++++++++++++++++++++++++
>>>> 2 files changed, 163 insertions(+), 37 deletions(-)
>>>>
>>>> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
>>>> index d71114f35..8978e674e 100644
>>>> --- a/northd/ovn-northd.c
>>>> +++ b/northd/ovn-northd.c
>>>> @@ -277,20 +277,16 @@ static struct gen_opts_map supported_dhcpv6_opts[] =
>>>> {
>>>> DHCPV6_OPT_FQDN,
>>>> };
>>>>
>>>> +/*
>>>> + * Compare predefined permission against RBAC_Permission record.
>>>> + * Returns true if match, false otherwise.
>>>> + */
>>>> static bool
>>>> -ovn_rbac_validate_perm(const struct sbrec_rbac_permission *perm)
>>>> +ovn_rbac_match_perm(const struct sbrec_rbac_permission *perm,
>>>> + const struct rbac_perm_cfg *pcfg)
>>>> {
>>>> - struct rbac_perm_cfg *pcfg;
>>>> int i, j, n_found;
>>>>
>>>> - for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
>>>> - if (!strcmp(perm->table, pcfg->table)) {
>>>> - break;
>>>> - }
>>>> - }
>>>> - if (!pcfg->table) {
>>>> - return false;
>>>> - }
>>>> if (perm->n_authorization != pcfg->n_auth ||
>>>> perm->n_update != pcfg->n_update) {
>>>> return false;
>>>> @@ -326,54 +322,74 @@ ovn_rbac_validate_perm(const struct
>>>> sbrec_rbac_permission *perm)
>>>> return false;
>>>> }
>>>>
>>>> - /* Success, db state matches expected state */
>>>> - pcfg->row = perm;
>>>> return true;
>>>> }
>>>>
>>>> +/*
>>>> + * Search predefined permission pcfg in the RBAC_Permission.
>>>> + * If there is no record that match, recover the permission.
>>>> + */
>>>> static void
>>>> -ovn_rbac_create_perm(struct rbac_perm_cfg *pcfg,
>>>> - struct ovsdb_idl_txn *ovnsb_txn,
>>>> - const struct sbrec_rbac_role *rbac_role)
>>>> +ovn_rbac_validate_perm(struct rbac_perm_cfg *pcfg,
>>>> + struct ovsdb_idl_txn *ovnsb_txn,
>>>> + struct ovsdb_idl *ovnsb_idl)
>>>> {
>>>> - struct sbrec_rbac_permission *rbac_perm;
>>>> + const struct sbrec_rbac_permission *perm_row;
>>>>
>>>> - rbac_perm = sbrec_rbac_permission_insert(ovnsb_txn);
>>>> - sbrec_rbac_permission_set_table(rbac_perm, pcfg->table);
>>>> - sbrec_rbac_permission_set_authorization(rbac_perm,
>>>> + SBREC_RBAC_PERMISSION_FOR_EACH_SAFE (perm_row, ovnsb_idl) {
>>> No need for _SAFE here.
>>>
>>>> + if (!strcmp(perm_row->table, pcfg->table)
>>>> + && ovn_rbac_match_perm(perm_row, pcfg)) {
>>>> + pcfg->row = perm_row;
>>>> +
>>>> + return;
>>>> + }
>>>> + }
>>>> +
>>>> + pcfg->row = sbrec_rbac_permission_insert(ovnsb_txn);
>>>> + sbrec_rbac_permission_set_table(pcfg->row, pcfg->table);
>>>> + sbrec_rbac_permission_set_authorization(pcfg->row,
>>>> pcfg->auth,
>>>> pcfg->n_auth);
>>>> - sbrec_rbac_permission_set_insert_delete(rbac_perm, pcfg->insdel);
>>>> - sbrec_rbac_permission_set_update(rbac_perm,
>>>> + sbrec_rbac_permission_set_insert_delete(pcfg->row, pcfg->insdel);
>>>> + sbrec_rbac_permission_set_update(pcfg->row,
>>>> pcfg->update,
>>>> pcfg->n_update);
>>>> - sbrec_rbac_role_update_permissions_setkey(rbac_role, pcfg->table,
>>>> - rbac_perm);
>>>> }
>>>>
>>>> +/*
>>>> + * Make sure that DB Role 'ovn-controller' exists, has no duplicates
>>>> + * permission list exactly match to predefined permissions. Recreate if
>>>> + * matching fails.
>>>> + */
>>>> static void
>>>> check_and_update_rbac(struct ovsdb_idl_txn *ovnsb_txn,
>>>> struct ovsdb_idl *ovnsb_idl)
>>>> {
>>>> const struct sbrec_rbac_role *rbac_role = NULL;
>>>> - const struct sbrec_rbac_permission *perm_row;
>>>> const struct sbrec_rbac_role *role_row;
>>>> struct rbac_perm_cfg *pcfg;
>>>> + bool rebuild_role_perm = false;
>>>> + int pcfg_len, i;
>>>>
>>>> + /*
>>>> + * Make sure predefined permissions are presented in the
>>>> RBAC_Permissions
>>>> + * table. Otherwise create consistent permissions.
>>>> + */
>>>> for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
>>>> - pcfg->row = NULL;
>>>> + ovn_rbac_validate_perm(pcfg, ovnsb_txn, ovnsb_idl);
>>>> }
>>>>
>>>> - SBREC_RBAC_PERMISSION_FOR_EACH_SAFE (perm_row, ovnsb_idl) {
>>>> - if (!ovn_rbac_validate_perm(perm_row)) {
>>>> - sbrec_rbac_permission_delete(perm_row);
>>>> - }
>>>> - }
>>>> + /*
>>>> + * Make sure the role 'ovn-controller' is presented in the RBAC_Role
>>>> table.
>>>> + * Otherwise create the role. Remove duplicates if any.
>>>> + */
>>>> SBREC_RBAC_ROLE_FOR_EACH_SAFE (role_row, ovnsb_idl) {
>>>> - if (strcmp(role_row->name, "ovn-controller")) {
>>>> - sbrec_rbac_role_delete(role_row);
>>>> - } else {
>>>> - rbac_role = role_row;
>>>> + if (!strcmp(role_row->name, "ovn-controller")) {
>>>> + if (rbac_role) {
>>>> + sbrec_rbac_role_delete(role_row);
>>>> + } else {
>>>> + rbac_role = role_row;
>>>> + }
>>>> }
>>>> }
>>>>
>>>> @@ -382,9 +398,39 @@ check_and_update_rbac(struct ovsdb_idl_txn *ovnsb_txn,
>>>> sbrec_rbac_role_set_name(rbac_role, "ovn-controller");
>>>> }
>>>>
>>>> - for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
>>>> - if (!pcfg->row) {
>>>> - ovn_rbac_create_perm(pcfg, ovnsb_txn, rbac_role);
>>>> + /*
>>>> + * Make sure a permission list attached to the role 'ovn-controller'
>>> Nit: s/Make sure a permission/Make sure the permission/
>>>
>>>> + * exactly matches to predefined permissions.
>>>> + * Reassign permission list to the role if any difference has found.
>>>> + */
>>>> + for (pcfg = rbac_perm_cfg, pcfg_len = 0; pcfg->table; pcfg++,
>>>> pcfg_len++) {
>>>> + for (i = 0; i < rbac_role->n_permissions; i++) {> + if
>>>> (strcmp(pcfg->table, rbac_role->key_permissions[i]) == 0
>>>> + && pcfg->row == rbac_role->value_permissions[i]) {
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + if (i == rbac_role->n_permissions) {
>>>> + rebuild_role_perm = true;
>>>> + break;
>>>> + }
>>>> + }
>>>> +
>>>> + if (pcfg_len != rbac_role->n_permissions) {
>>>> + rebuild_role_perm = true;
>>>> + }
>>>> +
>>>> + /*
>>>> + * Rebuild role's permission list.
>>>> + */
>>>> + if (rebuild_role_perm) {
>>>> + sbrec_rbac_role_set_permissions(rbac_role, NULL, NULL, 0);
>>>> +
>>>> + for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) {
>>>> + sbrec_rbac_role_update_permissions_setkey(rbac_role,
>>>> + pcfg->table,
>>>> + pcfg->row);
>>>> }
>>>> }
>>> I think I'd rewrite this part as:
>>>
>>> [...]
>>> if (ARRAY_SIZE(rbac_perm_cfg) != rbac_role->n_permissions) {
>>> goto rebuild_role_perms;
>>> }
>>>
>>> for (struct rbac_perm_cfg *pcfg = rbac_perm_cfg; pcfg->table; pcfg++)
>>> {
>>> size_t i;
>>> for (i = 0; i < rbac_role->n_permissions; i++) {
>>> if (!strcmp(pcfg->table, rbac_role->key_permissions[i])
>>> && pcfg->row == rbac_role->value_permissions[i]) {
>>> break;
>>> }
>>> }
>>>
>>> if (i == rbac_role->n_permissions) {
>>> goto rebuild_role_perms;
>>> }
>>> }
>>>
>>> return;
>>>
>>> rebuild_role_perms:
>>> sbrec_rbac_role_set_permissions(rbac_role, NULL, NULL, 0);
>>>
>>> for (struct rbac_perm_cfg *pcfg = rbac_perm_cfg; pcfg->table; pcfg++)
>>> {
>>> sbrec_rbac_role_update_permissions_setkey(rbac_role,
>>> pcfg->table,
>>> pcfg->row);
>>> }
>>> }
>>>
>>> What do you think?
>> I think your proposal is good if we feel ourselves comfortable with
>> 'goto' logic. Also I had imagination that local variables needs to be
>> gathered at top of function. If it is wrong I totally agree with your
>> change.
>>
> We try to avoid gotos in general but for situations like this one when
> they simplify the code and the control path is clear they're fine. We
> use them in similar ways in a few places in the code base.
>
> Regarding variables, we should try to define them such that we minimize
> as much as possiblethe scope they can be used in. This is partially
> covered by our coding style guidelines:
>
> https://github.com/ovn-org/ovn/blob/main/Documentation/internals/contributing/coding-style.rst#c-dialect
>
>>>> }
>>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>>> index d459c23c0..3c6b719c1 100644
>>>> --- a/tests/ovn-northd.at
>>>> +++ b/tests/ovn-northd.at
>>>> @@ -13867,3 +13867,83 @@ check_no_redirect
>>>>
>>>> AT_CLEANUP
>>>> ])
>>>> +
>>>> +AT_SETUP([RBAC -- Recover builtin role and permissions])
>>>> +ovn_start
>>>> +
>>>> +RBR_BUILTIN_PNAMES=`ovn-sbctl get RBAC_Role ovn-controller permissions |
>>>> uuidfilt | sed -e 's/<[[^<>]]>*//g' | tr -d '=,{}'`
>>> This can be simplified a bit by using fetch_column:
>>> RBR_BUILTIN_PNAMES=$(fetch_column RBAC_Role permissions name=ovn-controller
>>> | uuidfilt | sed -e 's/<[[^<>]]>*//g' | tr -d '=,{}')
>>>
>>> The same comment applies to multiple instances below.
>>>
>>>> +declare -A RBR_BUILTIN_PERM
>>>> +
>>>> +for V in $RBR_BUILTIN_PNAMES
>>>> +do
>>>> + _UUID=`ovn-sbctl get RBAC_Role ovn-controller permissions:$V`
>>>> + RBR_BUILTIN_PERM[[$V]]=`ovn-sbctl get RBAC_Permission $_UUID
>>>> authorization insert_delete table update | paste -s -d " "`
>>>> +done
>>>> +
>>>> +rbr_match_builtin() {
>>>> + local RBR_REAL_PNAMES=`ovn-sbctl get RBAC_Role ovn-controller
>>>> permissions | uuidfilt | sed -e 's/<[[^<>]]>*//g' | tr -d '=,{}'`
>>>> +
>>>> + AT_CHECK([test "$RBR_BUILTIN_PNAMES" == "$RBR_REAL_PNAMES"])
>>>> +
>>>> + for V in $RBR_BUILTIN_PNAMES
>>>> + do
>>>> + local _UUID=`ovn-sbctl get RBAC_Role ovn-controller permissions:$V`
>>>> + local RBR_REAL_PERM=`ovn-sbctl get RBAC_Permission $_UUID
>>>> authorization insert_delete table update | paste -s -d " "`
>>>> + AT_CHECK([test "${RBR_BUILTIN_PERM[[$V]]}" == "$RBR_REAL_PERM"])
>>>> + done
>>>> +}
>>>> +
>>>> +rbr_match_custom() {
>>>> + local RBR_BUILTIN_PNAMES='Load_Balancer'
>>>> + local RBR_BUILTIN_ROLE='{Load_Balancer=<0>}'
>>>> + local RBR_REAL_ROLE=`ovn-sbctl get RBAC_Role custom-role permissions |
>>>> uuidfilt`
>>>> + declare -A RBR_BUILTIN_PERM=(
>>>> + [[Load_Balancer]]='[[]] false Load_Balancer [[]]'
>>>> + )
>>>> +
>>>> + AT_CHECK([test "$RBR_BUILTIN_ROLE" == "$RBR_REAL_ROLE"])
>>>> +
>>>> + for V in $RBR_BUILTIN_PNAMES
>>>> + do
>>>> + local _UUID=`ovn-sbctl get RBAC_Role custom-role permissions:$V`
>>>> + local _PREAL=`ovn-sbctl get RBAC_Permission $_UUID authorization
>>>> insert_delete table update | paste -s -d " "`
>>>> + AT_CHECK([test "${RBR_BUILTIN_PERM[[$V]]}" == "$_PREAL"])
>>>> + done
>>>> +}
>>>> +
>>>> +AS_BOX([Recover after role delete])
>>>> +check ovn-sbctl destroy RBAC_Role ovn-controller
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Recover after permissions alternation])
>>>> +check ovn-sbctl remove RBAC_Permission Chassis update
>>>> vtep_logical_switches
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Recover after permission delete])
>>>> +PERM_UID=`ovn-sbctl get RBAC_Role ovn-controller permissions:Chassis`
>>>> +check check ovn-sbctl destroy RBAC_Permission $PERM_UID
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Recover after permission link removal from role])
>>>> +check ovn-sbctl remove RBAC_Role ovn-controller permissions Chassis
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Clean unwanted permission added to role])
>>>> +AT_CHECK([ovn-sbctl --id=@nr create RBAC_Permission table=Load_Balancer \
>>>> +-- set RBAC_Role ovn-controller permissions:Load_Balancer=@nr |
>>>> uuidfilt], [0], [<0>
>>>> +])
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Clean duplicated role])
>>>> +AT_CHECK([ovn-sbctl create RBAC_Role name=ovn-controller | uuidfilt],
>>>> [0], [<0>
>>>> +])
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +
>>>> +AS_BOX([Ensure custom role and permission are not automatically deleted])
>>>> +PERM_UID=`ovn-sbctl get RBAC_Permission Load_Balancer _uuid`
>>>> +ovn-sbctl create RBAC_Role name=custom-role
>>>> permissions:Load_Balancer=$PERM_UID
>>>> +OVS_WAIT_UNTIL([rbr_match_builtin])
>>>> +check rbr_match_custom
>>>> +
>>>> +AT_CLEANUP
>>>> +
>>> Regards,
>>> Dumitru
>>>
> Thanks,
> Dumitru
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev