[apparmor] [PATCH v2] json support for tools (logprof and genprof)

Goldwyn Rodrigues rgoldwyn at suse.de
Mon Apr 3 15:38:24 UTC 2017



On 04/02/2017 08:48 AM, Christian Boltz wrote:
> Hello,
> 
> Am Mittwoch, 22. März 2017, 19:24:04 CEST schrieb Goldwyn Rodrigues:
>> From: Goldwyn Rodrigues <rgoldwyn at suse.com>
>>
>> This adds JSON support for tools in order to be able to talk to
>> other utilities such as Yast.
>>
>> The json is one per line, in order to differentiate between multiple
>> records. This is based on work presented by Christian Boltz some time
>> back.
>>
>> Signed-off-by: Goldwyn Rodrigues <rgoldwyn at suse.com>
>>
>> ---
>> Changes since v1:
>>  - implementation of set_json_mode(), write_json()
>>  - Changed the way output is provided to keep input the same. This
>> would write in either of two formats: text or json, but will keep
>> input the same. This helps in keeping localizations in place. I so
>> wish UI was a class.. - Removed all yast calls.
> 
> A generic notice first: make check complained about some whitespace 
> issues. Please always run "make check" in the utils directory before 
> sending a patch ;-)  (even if your changes are probably not covered by 
> unittests)
> 
> Speaking about tests - I'd still like to see an unittest for the json 
> interface ;-)  (maybe a sample to-be-updated profile, an audit.log sniplet, 
> the expected JSON, predefined answers and finally the expected profile.)
> 
> 
> I tried to use aa-logprof --json, but all I get is:
> 
> python3 aa-logprof --json
> Traceback (most recent call last):
>   File "aa-logprof", line 56, in <module>
>     apparmor.do_logprof_pass(logmark)
>   File "/home/cb/apparmor/HEAD-clean/utils/apparmor/aa.py", line 1905, in do_logprof_pass
>     aaui.UI_Info(_('Reading log entries from %s.') % logfile)
>   File "/home/cb/apparmor/HEAD-clean/utils/apparmor/ui.py", line 61, in UI_Info
>     write_json(jsonout)
>   File "/home/cb/apparmor/HEAD-clean/utils/apparmor/ui.py", line 43, in write_json
>     sys.stdout.write(json.dumps(jsonout, soft_keys=False, separators=(',', ': ')) + '\n')
>   File "/usr/lib64/python3.6/json/__init__.py", line 238, in dumps
>     **kw).encode(obj)
> TypeError: __init__() got an unexpected keyword argument 'soft_keys'
> 
> Is that meant to be sort_keys instead of soft_keys?
> 
> After changing that, it finally worked. I get a prompt like (linebreaks 
> added for readability)
> 
> {
> "title": null,
> "headers": {"Profil": "/usr/bin/konversation","Pfad": "/sys/devices/pci0000:00/0000:00:02.0/vendor","Neuer Modus": "r","Schweregrad": 4},
> "explanation": null,
> "options": ["/sys/devices/pci0000:00/0000:00:02.0/vendor r,","/sys/devices/pci*/*/vendor r,"],
> "menu_items": ["Erl(a)uben","[A(b)lehnen]","(I)gnorieren","(G)lob","Glob with (E)xtension","(N)eu","Audi(t)","Abb(r)echen","En(d)e"]
> }
> 
> I'm missing one thing here - the preselected option.
> In most cases, the first option is preselected - but after using (G)lob,
> Glob with (E)xtension or (N)ew, the newly added option will be 
> selected, and the new option is in most (not all!) cases the last one.
> 
>> diff --git a/utils/apparmor/ui.py b/utils/apparmor/ui.py
>> index bfbde8c..e6fcd1c 100644
>> --- a/utils/apparmor/ui.py
>> +++ b/utils/apparmor/ui.py
> 
>> -from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast
> 
> It seems ui.py was the only user of yastLog(), so you can (and should)
> drop it from yasti.py.
> 
> SendDataToYast and GetDataFromYastcheck if you need to change something there.
> 
> From a quick look, the most important affected functions are:
> - get_profile() - you'll see this in action with aa-genprof if a profile
>   exists in /usr/share/apparmor/extra-profiles/ and you select 
>   "view profile"
> - yast_select_and_upload_profiles() - dead code because we don't have
>   a profile repo currently
> - save_profiles() - SendDataToYaST is used for "view changes"
> 
> There are some more functions that check for YaST - simply search aa.py
> for "Yast" and "yast".
> 
> Note: Ideally, aa.py should know nothing about the UI used, so it might
> be a good idea to move this code to ui.py unless that makes things 
> terribly difficult.
> 
> I'd guess a (to-be-written) UI_view_in_pager() function or extending 
> UI_LongMessage() to handle text mode (instead of deleting it) could 
> get the job done ;-)
> 
>> @@ -47,18 +56,17 @@ def UI_Info(text):
>>      debug_logger.info(text)
>>      if UI_mode == 'text':
>>          sys.stdout.write(text + '\n')
>> -    else:
>> -        yastLog(text)
>> +    elif UI_mode == 'json':
>> +        jsonout = {'info': text}
>> +        write_json(jsonout)
> 
> What happens if someone accidently sets UI_mode to "foo"? (Hint: no output,
> and you'll have a hard time to find out _why_ there isn't any output.)
> I know this is unlikely, but I'd still prefer to be on the safe side.
> 
> What about:
> 
>     if UI_mode == 'json':
>         ...
>     else:  # text mode
>         ...
> 
> You could also add an else branch that raises an exception("unknown 
> UI_mode") - but that might be too much ;-)
> 
> This comment applies to several sections, so please adjust this in the
> whole file.
> 
>> @@ -138,36 +140,31 @@ def UI_YesNoCancel(text, default):
>> ...
>> +            jsonout = {'dialog': 'yesnocancal', 'text': text, 'default': default}
> 
> As Seth already noted in v1, this should be yesnocanc_e_l ;-)
> 
>> @@ -405,7 +388,10 @@ class PromptQuestion(object):
>>
>>              prompt += ' / '.join(menu_items)
>>
>> -            sys.stdout.write(prompt + '\n')
>> +            if UI_mode == 'json':
>> +                sys.stdout.write(json.dumps(jsonprompt,
>> sort_keys=False,  separators=(',', ': ')) + '\n') 
> 
> Please use write_json() ;-)
> 
> 
> That all said - v2 looks much better than v1 :-)
> 

This is getting more complicated than can be handled. Do you think
converting UI module to an abstract class, and deriving JSONUI and
TextUI will be a better option? Though it would need more work, it would
simplify a lot of things, be more flexible to changes and would be more
robust than the hackish way it is being performed now.

Finally, is it a principle to not "reply to all" on the mailing list?
While the mail does reach me, The ML filters move the mails to respctive
folders.


-- 
Goldwyn



More information about the AppArmor mailing list