On 11/8/24 12:26, Aleksandr Smirnov (K2 Cloud) wrote:
> 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?
>
>From my perspective, the patch would be OK to merge once my comments are
addressed. I only explicitly asked for other maintainers to have a look
because I think it's a critical area and I wanted to make sure I'm not
missing anything important.
I think you should post v2 already if that's OK with you too.
Thanks!
>
>>
>>> 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