[yast-devel] [PATCH 0/3] yast-apparmor: Remove Perl dependency
Apparmor has come a long way since we wrote yast scripts for apparmor. For one, it has changed most of its utils to use python instead of perl. perl libraries in apparmor are no longer maintained and soon will be removed. In order to get yast up to times with apparmor changes, I propse the following patches. The patches are not up to mark to be included immediately, so I decided to send it here so it gets a better review as opposed to a pull request. Mostly, I think it can be improved in layout. I worked with the apparmor team to include JSON in the tools which is under review and will be included soon. Tools such as aa-logprof and aa-genprof are interactive, like a question-response session and hence need libraries like ruby popen to handle that. The communication is in JSON and is converted to a Yast dialog and the response is converted to a JSON response the tool expects. We will lose the ability to edit a profile (which was not complete currently). The prime reason being we cannot generate a complete list of changes we can have and such things are ever-changing. Besides, we don't want to add the logic of validating keywords in Yast. If there is a demand, we can consider options possible. TODO: In the future, I intend to add generating profiles for new programs. This would ease the process of adding new profiles. -- Goldwyn -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
From: Goldwyn Rodrigues <rgoldwyn@suse.com> This is a precursor to use aa-status json profile view. Please note, "Configure" option is left hanging which will be filled in the next patch. Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor-settings.rb | 139 ++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 54 deletions(-) diff --git a/src/clients/apparmor-settings.rb b/src/clients/apparmor-settings.rb index ecea5bc..2b2d3f2 100644 --- a/src/clients/apparmor-settings.rb +++ b/src/clients/apparmor-settings.rb @@ -1,8 +1,6 @@ -# encoding: utf-8 - # *************************************************************************** # -# Copyright (c) 2002 - 2012 Novell, Inc. +# Copyright (c) 2017 SUSE Linux # All Rights Reserved. # # This program is free software; you can redistribute it and/or @@ -17,71 +15,104 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com +# To contact SuSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com # # *************************************************************************** -module Yast - class ApparmorSettingsClient < Client - def main - Yast.import "UI" - - textdomain "yast2-apparmor" - # The main () - Builtins.y2milestone("----------------------------------------") - Builtins.y2milestone("AppArmor module started") +require "yast" - Yast.import "Label" - Yast.import "Popup" - Yast.import "Wizard" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" +Yast.import "Service" - Yast.include self, "apparmor/apparmor_packages.rb" - Yast.include self, "apparmor/aa-config.rb" +module AppArmor + class ApparmorSettings + include Yast::I18n + include Yast::UIShortcuts - # no command line support #269891 - if Ops.greater_than(Builtins.size(WFM.Args), 0) - Yast.import "CommandLine" - CommandLine.Init({}, WFM.Args) - return + def initialize + @service_enabled = Yast::Service.Enabled("apparmor") + end + def run + return unless create_dialog + begin + return event_loop + ensure + Yast::UI.CloseDialog end - - return if !installAppArmorPackages - - @config_steps = [ - { "id" => "apparmor", "label" => _("Enable AppArmor Functions") } - ] - - @steps = Builtins.flatten([@config_steps]) - - @current_step = 0 - @button = displayPage(@current_step) - - # Finish - Builtins.y2milestone("AppArmor module finished") - Builtins.y2milestone("----------------------------------------") - - # EOF - - nil end - def displayPage(no) - current_id = Ops.get_string(Ops.get(@steps, no), "id", "") - button = nil - - UI.WizardCommand(term(:SetCurrentStep, current_id)) - - if current_id == "apparmor" - #button = displayAppArmorConfig(); - button = displayAppArmorConfig + private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Heading(_("Apparmor Settings")), + VSpacing(1), + VBox( + CheckBox(Id(:aaState), Opt(:notify), _("&Enable Apparmor"), @service_enabled) + ), + VSpacing(1), + Frame( + Id(:aaEnableFrame), + _("Configure Profiles"), + HBox( + Label(_("Configure Profile modes")), + PushButton(Id(:modeconf), _("Configure")), + ) + ), + VSpacing(1), + HBox( + Right( + PushButton(Id(:quit), Yast::Label.QuitButton) + ) + ) + ) + ) + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, @service_enabled) + end + def event_loop + loop do + case Yast::UI.UserInput + when :modeconf + break + when :quit + break + end + @service_enabled = Yast::UI.QueryWidget(:aaState, :Value) + change_state end + end + def change_state + status = Yast::Service.Enabled("apparmor") + # If the service is the same state as our status, return + if status == @service_enabled + return + end + # Change the state to what we have + if @service_enabled + Yast::Service.start("apparmor") + Yast::Service.enable("apparmor") + else + Yast::Service.stop("apparmor") + Yast::Service.disable("apparmor") + end - button + # Check if the change of service state worked + status = Yast::Service.Enabled("apparmor") + if status != @service_enabled + Yast::Report.Error(_('Failed to change apparmor service. Please use journal (journalctl -n -u apparmor) to diagnose')) + else + # Enable the configuration frame since everything went well + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, status) + end end + end end -Yast::ApparmorSettingsClient.new.main +AppArmor::ApparmorSettings.new.run -- 2.12.3 -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
On Wed, 7 Jun 2017 15:38:45 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
This is a precursor to use aa-status json profile view.
Please note, "Configure" option is left hanging which will be filled in the next patch.
Well, to be honest I found discussion in pull request easier to orientate and also for discussion, but lets comment it here.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor-settings.rb | 139 ++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 54 deletions(-)
diff --git a/src/clients/apparmor-settings.rb b/src/clients/apparmor-settings.rb index ecea5bc..2b2d3f2 100644 --- a/src/clients/apparmor-settings.rb +++ b/src/clients/apparmor-settings.rb @@ -1,8 +1,6 @@ -# encoding: utf-8 - # *************************************************************************** # -# Copyright (c) 2002 - 2012 Novell, Inc. +# Copyright (c) 2017 SUSE Linux # All Rights Reserved. # # This program is free software; you can redistribute it and/or @@ -17,71 +15,104 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com +# To contact SuSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com # # *************************************************************************** -module Yast - class ApparmorSettingsClient < Client - def main - Yast.import "UI" - - textdomain "yast2-apparmor"
- # The main () - Builtins.y2milestone("----------------------------------------") - Builtins.y2milestone("AppArmor module started") +require "yast"
- Yast.import "Label" - Yast.import "Popup" - Yast.import "Wizard" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" +Yast.import "Service"
- Yast.include self, "apparmor/apparmor_packages.rb" - Yast.include self, "apparmor/aa-config.rb" +module AppArmor + class ApparmorSettings + include Yast::I18n + include Yast::UIShortcuts
- # no command line support #269891 - if Ops.greater_than(Builtins.size(WFM.Args), 0) - Yast.import "CommandLine" - CommandLine.Init({}, WFM.Args) - return + def initialize + @service_enabled = Yast::Service.Enabled("apparmor") + end + def run + return unless create_dialog + begin + return event_loop + ensure + Yast::UI.CloseDialog end
I really suggest to use Dialog class as ancestor, it will make your life easier: http://www.rubydoc.info/github/yast/yast-yast2/UI/Dialog it already have similar code implemented.
- - return if !installAppArmorPackages - - @config_steps = [ - { "id" => "apparmor", "label" => _("Enable AppArmor Functions") } - ] - - @steps = Builtins.flatten([@config_steps]) - - @current_step = 0 - @button = displayPage(@current_step) - - # Finish - Builtins.y2milestone("AppArmor module finished") - Builtins.y2milestone("----------------------------------------") - - # EOF - - nil end
- def displayPage(no) - current_id = Ops.get_string(Ops.get(@steps, no), "id", "") - button = nil - - UI.WizardCommand(term(:SetCurrentStep, current_id)) - - if current_id == "apparmor" - #button = displayAppArmorConfig(); - button = displayAppArmorConfig + private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Heading(_("Apparmor Settings")), + VSpacing(1), + VBox( + CheckBox(Id(:aaState), Opt(:notify), _("&Enable Apparmor"), @service_enabled) + ), + VSpacing(1), + Frame( + Id(:aaEnableFrame), + _("Configure Profiles"), + HBox( + Label(_("Configure Profile modes")), + PushButton(Id(:modeconf), _("Configure")), + ) + ), + VSpacing(1), + HBox( + Right( + PushButton(Id(:quit), Yast::Label.QuitButton) + ) + ) + ) + ) + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, @service_enabled) + end
I suggest to use rubocop for checking coding style style. Here for example missing empty line, which is quite hard to read. For example rubocop config please see: https://github.com/yast/yast-bootloader/blob/master/.rubocop.yml for usage just run rubocop command. Hint: `rubocop --auto-correct` will fix coding style where it is safe.
+ def event_loop + loop do + case Yast::UI.UserInput + when :modeconf + break + when :quit + break
you can write it as `break if [:modeconf, :quit].include?(Yast::UI.UserInput)` but I suggest to store UserInput outside of it as it have side-efect, so it should be more visible.
+ end + @service_enabled = Yast::UI.QueryWidget(:aaState, :Value) + change_state end + end
+ def change_state + status = Yast::Service.Enabled("apparmor") + # If the service is the same state as our status, return + if status == @service_enabled + return + end
+ # Change the state to what we have + if @service_enabled + Yast::Service.start("apparmor") + Yast::Service.enable("apparmor") + else + Yast::Service.stop("apparmor") + Yast::Service.disable("apparmor") + end
- button + # Check if the change of service state worked + status = Yast::Service.Enabled("apparmor")
Just note. It is quite uncommon in yast to react immediatelly in YaST UI. Especially consider that user click on "Enable" checkbox and then press cancel. This should be done only after click on next, where he confirm that he really want to do actions.
+ if status != @service_enabled + Yast::Report.Error(_('Failed to change apparmor service. Please use journal (journalctl -n -u apparmor) to diagnose')) + else + # Enable the configuration frame since everything went well + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, status) + end end + end end
-Yast::ApparmorSettingsClient.new.main +AppArmor::ApparmorSettings.new.run
Btw, recent practive in yast is to have really small client ( like two lines ``` require "y2apparmor/clients/my_client" Y2Apparmor::Clients::MyClient.run ``` and having code for client in lib/y2apparmor/clients/my_client.rb. This will allow much easier unit testing of code in client ( as require do not run code automatic ) -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
On 06/08/2017 09:33 AM, Josef Reidinger wrote:
On Wed, 7 Jun 2017 15:38:45 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
This is a precursor to use aa-status json profile view.
Please note, "Configure" option is left hanging which will be filled in the next patch.
Well, to be honest I found discussion in pull request easier to orientate and also for discussion, but lets comment it here.
Definitely. What's the advantage of a mailing list in comparison with Github?
[...]
+ @service_enabled = Yast::UI.QueryWidget(:aaState, :Value) + change_state end + end
+ def change_state + status = Yast::Service.Enabled("apparmor") + # If the service is the same state as our status, return + if status == @service_enabled + return + end
+ # Change the state to what we have + if @service_enabled + Yast::Service.start("apparmor") + Yast::Service.enable("apparmor") + else + Yast::Service.stop("apparmor") + Yast::Service.disable("apparmor") + end
- button + # Check if the change of service state worked + status = Yast::Service.Enabled("apparmor")
Just note. It is quite uncommon in yast to react immediatelly in YaST UI. Especially consider that user click on "Enable" checkbox and then press cancel. This should be done only after click on next, where he confirm that he really want to do actions.
Moreover, for starting/stopping and enabling/disabling services we already have a common widget (with wording and behavior blessed by the UX team) used in both yast-dhcp-server and yast-dns-server. Here is the (quite extensively documented) class: https://github.com/yast/yast-yast2/blob/master/library/general/src/lib/ui/se... And here is the unit test, that should be useful as example (in addition to the already mentioned modules already using it): https://github.com/yast/yast-yast2/blob/master/library/general/test/service_... Cheers. -- Ancor González Sosa YaST Team at SUSE Linux GmbH -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
On 06/08/2017 02:33 AM, Josef Reidinger wrote:
On Wed, 7 Jun 2017 15:38:45 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
This is a precursor to use aa-status json profile view.
Please note, "Configure" option is left hanging which will be filled in the next patch.
Well, to be honest I found discussion in pull request easier to orientate and also for discussion, but lets comment it here.
Sorry, coming from the kernel development community, I assumed a bigger subscription here and hence more eyeballs. I would perform a pull request in the next review cycle.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor-settings.rb | 139 ++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 54 deletions(-)
diff --git a/src/clients/apparmor-settings.rb b/src/clients/apparmor-settings.rb index ecea5bc..2b2d3f2 100644 --- a/src/clients/apparmor-settings.rb +++ b/src/clients/apparmor-settings.rb @@ -1,8 +1,6 @@ -# encoding: utf-8 - # *************************************************************************** # -# Copyright (c) 2002 - 2012 Novell, Inc. +# Copyright (c) 2017 SUSE Linux # All Rights Reserved. # # This program is free software; you can redistribute it and/or @@ -17,71 +15,104 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, contact Novell, Inc. # -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com +# To contact SuSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com # # *************************************************************************** -module Yast - class ApparmorSettingsClient < Client - def main - Yast.import "UI" - - textdomain "yast2-apparmor"
- # The main () - Builtins.y2milestone("----------------------------------------") - Builtins.y2milestone("AppArmor module started") +require "yast"
- Yast.import "Label" - Yast.import "Popup" - Yast.import "Wizard" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" +Yast.import "Service"
- Yast.include self, "apparmor/apparmor_packages.rb" - Yast.include self, "apparmor/aa-config.rb" +module AppArmor + class ApparmorSettings + include Yast::I18n + include Yast::UIShortcuts
- # no command line support #269891 - if Ops.greater_than(Builtins.size(WFM.Args), 0) - Yast.import "CommandLine" - CommandLine.Init({}, WFM.Args) - return + def initialize + @service_enabled = Yast::Service.Enabled("apparmor") + end + def run + return unless create_dialog + begin + return event_loop + ensure + Yast::UI.CloseDialog end
I really suggest to use Dialog class as ancestor, it will make your life easier: http://www.rubydoc.info/github/yast/yast-yast2/UI/Dialog
it already have similar code implemented.
- - return if !installAppArmorPackages - - @config_steps = [ - { "id" => "apparmor", "label" => _("Enable AppArmor Functions") } - ] - - @steps = Builtins.flatten([@config_steps]) - - @current_step = 0 - @button = displayPage(@current_step) - - # Finish - Builtins.y2milestone("AppArmor module finished") - Builtins.y2milestone("----------------------------------------") - - # EOF - - nil end
- def displayPage(no) - current_id = Ops.get_string(Ops.get(@steps, no), "id", "") - button = nil - - UI.WizardCommand(term(:SetCurrentStep, current_id)) - - if current_id == "apparmor" - #button = displayAppArmorConfig(); - button = displayAppArmorConfig + private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Heading(_("Apparmor Settings")), + VSpacing(1), + VBox( + CheckBox(Id(:aaState), Opt(:notify), _("&Enable Apparmor"), @service_enabled) + ), + VSpacing(1), + Frame( + Id(:aaEnableFrame), + _("Configure Profiles"), + HBox( + Label(_("Configure Profile modes")), + PushButton(Id(:modeconf), _("Configure")), + ) + ), + VSpacing(1), + HBox( + Right( + PushButton(Id(:quit), Yast::Label.QuitButton) + ) + ) + ) + ) + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, @service_enabled) + end
I suggest to use rubocop for checking coding style style. Here for example missing empty line, which is quite hard to read. For example rubocop config please see:
https://github.com/yast/yast-bootloader/blob/master/.rubocop.yml
for usage just run rubocop command. Hint: `rubocop --auto-correct` will fix coding style where it is safe.
+ def event_loop + loop do + case Yast::UI.UserInput + when :modeconf + break + when :quit + break
you can write it as `break if [:modeconf, :quit].include?(Yast::UI.UserInput)` but I suggest to store UserInput outside of it as it have side-efect, so it should be more visible.
+ end + @service_enabled = Yast::UI.QueryWidget(:aaState, :Value) + change_state end + end
+ def change_state + status = Yast::Service.Enabled("apparmor") + # If the service is the same state as our status, return + if status == @service_enabled + return + end
+ # Change the state to what we have + if @service_enabled + Yast::Service.start("apparmor") + Yast::Service.enable("apparmor") + else + Yast::Service.stop("apparmor") + Yast::Service.disable("apparmor") + end
- button + # Check if the change of service state worked + status = Yast::Service.Enabled("apparmor")
Just note. It is quite uncommon in yast to react immediatelly in YaST UI. Especially consider that user click on "Enable" checkbox and then press cancel. This should be done only after click on next, where he confirm that he really want to do actions.
I understand. However, I just followed the design how it exists now. While I am open to changing the design, it may take me much longer to learn and implement it. Let's keep it in the next round of changes, but as a guideline, I would like to have suggestions on how it should look like. How about yast-firewall? We will have to implement a separate "Manage profiles". There are more changes I want to introduce there as well such as multiple/different toggle modes.
+ if status != @service_enabled + Yast::Report.Error(_('Failed to change apparmor service. Please use journal (journalctl -n -u apparmor) to diagnose')) + else + # Enable the configuration frame since everything went well + Yast::UI.ChangeWidget(Id(:aaEnableFrame), :Enabled, status) + end end + end end
-Yast::ApparmorSettingsClient.new.main +AppArmor::ApparmorSettings.new.run
Btw, recent practive in yast is to have really small client ( like two lines ``` require "y2apparmor/clients/my_client"
Y2Apparmor::Clients::MyClient.run ```
and having code for client in lib/y2apparmor/clients/my_client.rb. This will allow much easier unit testing of code in client ( as require do not run code automatic )
Yes, I noticed that in the yast-journalctl tutorial. The way I divided it is to keep all yast-apparmor interactions in lib/ and keep the rest in clients. Does it have to be in lib/y2apparmor/clients or just lib/ would do as yast-journalctl tutorial mentions? -- Goldwyn -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
From: Goldwyn Rodrigues <rgoldwyn@suse.com> Handles the profiles using JSON output of aa-status. This replaces the earlier way of handling profiles. Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/AA_EditProfile.rb | 129 ---------------------- src/clients/apparmor-settings.rb | 3 +- src/clients/apparmor.rb | 1 - src/clients/profiles.rb | 111 +++++++++++++++++++ src/lib/apparmor/profiles.rb | 227 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 340 insertions(+), 131 deletions(-) delete mode 100644 src/clients/AA_EditProfile.rb create mode 100644 src/clients/profiles.rb create mode 100644 src/lib/apparmor/profiles.rb diff --git a/src/clients/AA_EditProfile.rb b/src/clients/AA_EditProfile.rb deleted file mode 100644 index a616143..0000000 --- a/src/clients/AA_EditProfile.rb +++ /dev/null @@ -1,129 +0,0 @@ -# encoding: utf-8 - -# *************************************************************************** -# -# Copyright (c) 2002 - 2012 Novell, Inc. -# All Rights Reserved. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com -# -# *************************************************************************** -module Yast - class AAEditProfileClient < Client - def main - Yast.import "UI" - Yast.import "Wizard" - Yast.import "Popup" - Yast.import "Label" - Yast.import "Sequencer" - Yast.include self, "apparmor/apparmor_packages.rb" - Yast.include self, "apparmor/apparmor_profile_check.rb" - Yast.include self, "apparmor/profile_dialogs.rb" - textdomain "yast2-apparmor" - - # Globalz - - @profiles = nil - - - - # - # YEAH BABY RUN BABY RUN - # - @ret = nil - - # no command line support #269891 - if Ops.greater_than(Builtins.size(WFM.Args), 0) - Yast.import "CommandLine" - CommandLine.Init({}, WFM.Args) - return deep_copy(@ret) - end - - return deep_copy(@ret) if !installAppArmorPackages - - return deep_copy(@ret) if !checkProfileSyntax - - - @ret = MainSequence() - deep_copy(@ret) - end - - def Reread - @profiles = Convert.to_map(SCR.Read(path(".apparmor_profiles"), "all")) - :next - end - - - def MainSequence - # - # Read the profiles from the SCR agent - Reread() - - aliases = { - "showProfile" => lambda do - DisplayProfileForm( - Ops.get_string(@Settings, "CURRENT_PROFILE", ""), - false - ) - end, - "showHat" => lambda do - DisplayProfileForm(Ops.get_string(@Settings, "CURRENT_HAT", ""), true) - end, - "chooseProfile" => lambda do - SelectProfileForm( - @profiles, - _("Select a listed profile and press Edit to edit it."), - _("Edit Profile - Choose profile to edit"), - "apparmor_edit_profile" - ) - end, - "reread" => lambda { Reread() } - } - - sequence = { - "ws_start" => "chooseProfile", - "chooseProfile" => { - :abort => :abort, - :edit => "showProfile", - :reread => "reread", - :next => :next - }, - "showProfile" => { - :abort => :abort, - :next => "reread", - :showhat => "showHat", - :finish => :next - }, - "reread" => { :next => "chooseProfile" }, - "showHat" => { - :abort => :abort, - :next => "showProfile", - :finish => :next - } - } - - Wizard.CreateDialog - Wizard.SetTitleIcon("apparmor_edit_profile") - ret = Sequencer.Run(aliases, sequence) - Wizard.CloseDialog - @Settings = Builtins.remove(@Settings, "CURRENT_PROFILE") - @Settings = Builtins.remove(@Settings, "PROFILE_MAP") - deep_copy(ret) - end - end -end - -Yast::AAEditProfileClient.new.main diff --git a/src/clients/apparmor-settings.rb b/src/clients/apparmor-settings.rb index 2b2d3f2..d3f6d1a 100644 --- a/src/clients/apparmor-settings.rb +++ b/src/clients/apparmor-settings.rb @@ -21,6 +21,7 @@ # *************************************************************************** require "yast" +require "apparmor/profiles" Yast.import "UI" Yast.import "Label" @@ -77,7 +78,7 @@ module AppArmor loop do case Yast::UI.UserInput when :modeconf - break + AppArmor::ProfilesDialog.new.run when :quit break end diff --git a/src/clients/apparmor.rb b/src/clients/apparmor.rb index 63e8874..1e6d7e3 100644 --- a/src/clients/apparmor.rb +++ b/src/clients/apparmor.rb @@ -70,7 +70,6 @@ module Yast [ # Selection box items Item(Id("apparmor-settings"), _("Settings"), true), - Item(Id("AA_EditProfile"), _("Manage Existing Profiles")), Item(Id("AA_AddProfile"), _("Manually Add Profile")) ] ), diff --git a/src/clients/profiles.rb b/src/clients/profiles.rb new file mode 100644 index 0000000..06c6e8a --- /dev/null +++ b/src/clients/profiles.rb @@ -0,0 +1,111 @@ + +require "apparmor/profiles" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +class ProfilesDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + + def initialize + @profiles = Apparmor::Profiles.new + @active = true + end + + def run + return unless create_dialog + + begin + return event_loop + ensure + close_dialog + end + end + +private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + #Header + Heading(_("Profile List")), + # Active profiles + Left( + CheckBox(Id(:active_only), Opt(:notify), _("Show Active only"), @active) + ), + VSpacing(0.4), + # Profile List + table, + VSpacing(0.3), + # Footer buttons + HBox( + HWeight(1, PushButton(Id(:toggle), _("Toggle mode"))), + HStretch(), + HWeight(1, PushButton(Id(:add), Yast::Label.AddButton)), + HStretch(), + HWeight(1, PushButton(Id(:edit), Yast::Label.EditButton)), + HStretch(), + HWeight(1, PushButton(Id(:finish), Yast::Label.FinishButton)) + ) + ) + ) + end + + def table + headers = Array[_("Name"), _("Mode"), _("PID")] + Table( + Id(:entries_table), + Opt(:keepSorting), + Header(*headers), + table_items + ) + end + + def table_items + if @active + profs = @profiles.active + else + profs = @profiles.all + end + arr = Array.new + profs.each do | n, pr | + arr.push(pr.to_array) + end + arr.map { |i| Item(*i) } + end + + def redraw_table + Yast::UI.ChangeWidget(Id(:entries_table), :Items, table_items) + end + + def event_loop + loop do + case Yast::UI.UserInput + when :active_only + @active = Yast::UI.QueryWidget(Id(:active_only), :Value) + redraw_table + when :cancel + break + when :toggle + selectedItem = Yast::UI.QueryWidget(Id(:entries_table), :CurrentItem) + log.info "Toggling #{selectedItem}" + @profiles.toggle(selectedItem) + redraw_table + when :edit + break + when :finish + break + end + end + end + + def close_dialog + Yast::UI.CloseDialog + end +end + +ProfilesDialog.new.run + + diff --git a/src/lib/apparmor/profiles.rb b/src/lib/apparmor/profiles.rb new file mode 100644 index 0000000..f3129f6 --- /dev/null +++ b/src/lib/apparmor/profiles.rb @@ -0,0 +1,227 @@ +# Get the status of profiles loaded +# - enforced +# - complain +# Uses aa-status --json + +require "json" +require "open3" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +module AppArmor + + class Profile + attr_reader :name, :status, :pid + + def initialize(name, status) + @name=name + @status=status + @pid = Array.new + end + + #Return/Set the name of the profile + def name(n="") + if n.length > 0 + @name = n + end + return @name + end + + #return the current status + def status(s="") + if s != "" + @status = s + end + return @status + end + + # Set to complain mode + def complain() + system("/usr/sbin/aa-complain #{@name}") + return self.status("complain") + end + + # Set to enforce mode + def enforce() + system("/usr/sbin/aa-enforce #{@name}") + return self.status("enforce") + end + + def addPid(p) + @pid.push(p) + end + + def pid + return @pid + end + + def toggle + if @status == "complain" + self.enforce + else + self.complain + end + end + + def to_s + @name + ", " + @status + ", " + @pid + end + + def to_array + a = Array.new + a.push(@name) + a.push(@status) + pstr = " " + # Convert PID array to a pretty string with commas + @pid.each do | p | + pstr = pstr + p.to_str + ", " + end + a.push(pstr) + return a + end + end + + class ProfileMap + def self.all(text) + jtext = JSON.parse(text) + h = jtext["profiles"] + entries = Hash.new + h.each do |name, status| + entries[name] = Profile.new(name, status) + end + h = jtext["processes"] + h.each do |name, pidmap| + pidmap.each do |p| + entries[name].addPid(p["pid"]) + end + end + return entries + end + end + + class Profiles + STATUS_CMD = "sudo /usr/sbin/aa-status --json" + attr_reader :raw, :prof + def initialize() + cmd = "#{STATUS_CMD}" + stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) + @prof = ProfileMap.all(stdout.read) + end + + def remove(name) + e = @prof.delete(name) + # execute file deletion? + end + + def active() + # Select the ones which have pids + @prof.select { | name, pr | pr.pid.length > 0 } + end + + def all() + return @prof + end + + def toggle(name) + @prof[name].toggle + end + end + + class ProfilesDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + + def initialize + @profiles = Profiles.new + @active = true + end + + def run + return unless create_dialog + + begin + return event_loop + ensure + close_dialog + end + end + + private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + #Header + Heading(_("Profile List")), + # Active profiles + Left( + CheckBox(Id(:active_only), Opt(:notify), _("Show Active only"), @active) + ), + VSpacing(0.4), + # Profile List + table, + VSpacing(0.3), + # Footer buttons + HBox( + HWeight(1, PushButton(Id(:changeMode), _("Change mode"))), + HStretch(), + HWeight(1, PushButton(Id(:finish), Yast::Label.FinishButton)) + ) + ) + ) + end + + def table + headers = Array[_("Name"), _("Mode"), _("PID")] + Table( + Id(:entries_table), + Opt(:keepSorting), + Header(*headers), + table_items + ) + end + + def table_items + if @active + profs = @profiles.active + else + profs = @profiles.all + end + arr = Array.new + profs.each do | n, pr | + arr.push(pr.to_array) + end + arr.map { |i| Item(*i) } + end + + def redraw_table + Yast::UI.ChangeWidget(Id(:entries_table), :Items, table_items) + end + + def event_loop + loop do + case Yast::UI.UserInput + when :active_only + @active = Yast::UI.QueryWidget(Id(:active_only), :Value) + redraw_table + when :cancel + break + when :changeMode + selectedItem = Yast::UI.QueryWidget(Id(:entries_table), :CurrentItem) + log.info "Toggling #{selectedItem}" + @profiles.toggle(selectedItem) + redraw_table + when :finish + break + end + end + end + + def close_dialog + Yast::UI.CloseDialog + end + end +end + -- 2.12.3 -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
On Wed, 7 Jun 2017 15:38:46 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
Handles the profiles using JSON output of aa-status. This replaces the earlier way of handling profiles.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/AA_EditProfile.rb | 129 ---------------------- src/clients/apparmor-settings.rb | 3 +- src/clients/apparmor.rb | 1 - src/clients/profiles.rb | 111 +++++++++++++++++++ src/lib/apparmor/profiles.rb | 227 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 340 insertions(+), 131 deletions(-) delete mode 100644 src/clients/AA_EditProfile.rb create mode 100644 src/clients/profiles.rb create mode 100644 src/lib/apparmor/profiles.rb
diff --git a/src/clients/AA_EditProfile.rb b/src/clients/AA_EditProfile.rb deleted file mode 100644 index a616143..0000000 --- a/src/clients/AA_EditProfile.rb +++ /dev/null @@ -1,129 +0,0 @@ -# encoding: utf-8 - -# *************************************************************************** -# -# Copyright (c) 2002 - 2012 Novell, Inc. -# All Rights Reserved. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public License as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com -# -# *************************************************************************** -module Yast - class AAEditProfileClient < Client - def main - Yast.import "UI" - Yast.import "Wizard" - Yast.import "Popup" - Yast.import "Label" - Yast.import "Sequencer" - Yast.include self, "apparmor/apparmor_packages.rb" - Yast.include self, "apparmor/apparmor_profile_check.rb" - Yast.include self, "apparmor/profile_dialogs.rb" - textdomain "yast2-apparmor" - - # Globalz - - @profiles = nil - - - - # - # YEAH BABY RUN BABY RUN - # - @ret = nil - - # no command line support #269891 - if Ops.greater_than(Builtins.size(WFM.Args), 0) - Yast.import "CommandLine" - CommandLine.Init({}, WFM.Args) - return deep_copy(@ret) - end - - return deep_copy(@ret) if !installAppArmorPackages - - return deep_copy(@ret) if !checkProfileSyntax - - - @ret = MainSequence() - deep_copy(@ret) - end - - def Reread - @profiles = Convert.to_map(SCR.Read(path(".apparmor_profiles"), "all")) - :next - end - - - def MainSequence - # - # Read the profiles from the SCR agent - Reread() - - aliases = { - "showProfile" => lambda do - DisplayProfileForm( - Ops.get_string(@Settings, "CURRENT_PROFILE", ""), - false - ) - end, - "showHat" => lambda do - DisplayProfileForm(Ops.get_string(@Settings, "CURRENT_HAT", ""), true) - end, - "chooseProfile" => lambda do - SelectProfileForm( - @profiles, - _("Select a listed profile and press Edit to edit it."), - _("Edit Profile - Choose profile to edit"), - "apparmor_edit_profile" - ) - end, - "reread" => lambda { Reread() } - } - - sequence = { - "ws_start" => "chooseProfile", - "chooseProfile" => { - :abort => :abort, - :edit => "showProfile", - :reread => "reread", - :next => :next - }, - "showProfile" => { - :abort => :abort, - :next => "reread", - :showhat => "showHat", - :finish => :next - }, - "reread" => { :next => "chooseProfile" }, - "showHat" => { - :abort => :abort, - :next => "showProfile", - :finish => :next - } - } - - Wizard.CreateDialog - Wizard.SetTitleIcon("apparmor_edit_profile") - ret = Sequencer.Run(aliases, sequence) - Wizard.CloseDialog - @Settings = Builtins.remove(@Settings, "CURRENT_PROFILE") - @Settings = Builtins.remove(@Settings, "PROFILE_MAP") - deep_copy(ret) - end - end -end - -Yast::AAEditProfileClient.new.main diff --git a/src/clients/apparmor-settings.rb b/src/clients/apparmor-settings.rb index 2b2d3f2..d3f6d1a 100644 --- a/src/clients/apparmor-settings.rb +++ b/src/clients/apparmor-settings.rb @@ -21,6 +21,7 @@ # ***************************************************************************
require "yast" +require "apparmor/profiles"
Yast.import "UI" Yast.import "Label" @@ -77,7 +78,7 @@ module AppArmor loop do case Yast::UI.UserInput when :modeconf - break + AppArmor::ProfilesDialog.new.run when :quit break end diff --git a/src/clients/apparmor.rb b/src/clients/apparmor.rb index 63e8874..1e6d7e3 100644 --- a/src/clients/apparmor.rb +++ b/src/clients/apparmor.rb @@ -70,7 +70,6 @@ module Yast [ # Selection box items Item(Id("apparmor-settings"), _("Settings"), true), - Item(Id("AA_EditProfile"), _("Manage Existing Profiles")), Item(Id("AA_AddProfile"), _("Manually Add Profile")) ] ), diff --git a/src/clients/profiles.rb b/src/clients/profiles.rb new file mode 100644 index 0000000..06c6e8a --- /dev/null +++ b/src/clients/profiles.rb @@ -0,0 +1,111 @@ + +require "apparmor/profiles" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +class ProfilesDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + + def initialize + @profiles = Apparmor::Profiles.new + @active = true + end + + def run + return unless create_dialog + + begin + return event_loop + ensure + close_dialog + end + end + +private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + #Header + Heading(_("Profile List")), + # Active profiles + Left( + CheckBox(Id(:active_only), Opt(:notify), _("Show Active only"), @active) + ), + VSpacing(0.4), + # Profile List + table, + VSpacing(0.3), + # Footer buttons + HBox( + HWeight(1, PushButton(Id(:toggle), _("Toggle mode"))), + HStretch(), + HWeight(1, PushButton(Id(:add), Yast::Label.AddButton)), + HStretch(), + HWeight(1, PushButton(Id(:edit), Yast::Label.EditButton)), + HStretch(), + HWeight(1, PushButton(Id(:finish), Yast::Label.FinishButton)) + ) + ) + ) + end + + def table + headers = Array[_("Name"), _("Mode"), _("PID")] + Table( + Id(:entries_table), + Opt(:keepSorting), + Header(*headers), + table_items + ) + end + + def table_items + if @active + profs = @profiles.active + else + profs = @profiles.all + end + arr = Array.new + profs.each do | n, pr | + arr.push(pr.to_array) + end + arr.map { |i| Item(*i) } + end + + def redraw_table + Yast::UI.ChangeWidget(Id(:entries_table), :Items, table_items) + end + + def event_loop + loop do + case Yast::UI.UserInput + when :active_only + @active = Yast::UI.QueryWidget(Id(:active_only), :Value) + redraw_table + when :cancel + break + when :toggle + selectedItem = Yast::UI.QueryWidget(Id(:entries_table), :CurrentItem) + log.info "Toggling #{selectedItem}" + @profiles.toggle(selectedItem) + redraw_table + when :edit + break + when :finish + break + end + end + end + + def close_dialog + Yast::UI.CloseDialog + end +end + +ProfilesDialog.new.run + + diff --git a/src/lib/apparmor/profiles.rb b/src/lib/apparmor/profiles.rb new file mode 100644 index 0000000..f3129f6 --- /dev/null +++ b/src/lib/apparmor/profiles.rb @@ -0,0 +1,227 @@ +# Get the status of profiles loaded +# - enforced +# - complain +# Uses aa-status --json + +require "json" +require "open3" +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +module AppArmor + + class Profile + attr_reader :name, :status, :pid + + def initialize(name, status) + @name=name + @status=status + @pid = Array.new + end + + #Return/Set the name of the profile + def name(n="") + if n.length > 0 + @name = n + end + return @name + end + + #return the current status + def status(s="") + if s != "" + @status = s + end + return @status + end + + # Set to complain mode + def complain() + system("/usr/sbin/aa-complain #{@name}") + return self.status("complain") + end + + # Set to enforce mode + def enforce() + system("/usr/sbin/aa-enforce #{@name}") + return self.status("enforce") + end + + def addPid(p) + @pid.push(p) + end + + def pid + return @pid + end + + def toggle + if @status == "complain" + self.enforce + else + self.complain + end + end + + def to_s + @name + ", " + @status + ", " + @pid + end + + def to_array + a = Array.new + a.push(@name) + a.push(@status) + pstr = " " + # Convert PID array to a pretty string with commas + @pid.each do | p | + pstr = pstr + p.to_str + ", " + end + a.push(pstr) + return a + end + end + + class ProfileMap + def self.all(text) + jtext = JSON.parse(text) + h = jtext["profiles"] + entries = Hash.new + h.each do |name, status| + entries[name] = Profile.new(name, status) + end + h = jtext["processes"] + h.each do |name, pidmap| + pidmap.each do |p| + entries[name].addPid(p["pid"]) + end + end + return entries + end + end + + class Profiles + STATUS_CMD = "sudo /usr/sbin/aa-status --json" + attr_reader :raw, :prof + def initialize() + cmd = "#{STATUS_CMD}" + stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) + @prof = ProfileMap.all(stdout.read) + end + + def remove(name) + e = @prof.delete(name) + # execute file deletion? + end + + def active() + # Select the ones which have pids + @prof.select { | name, pr | pr.pid.length > 0 } + end + + def all() + return @prof + end + + def toggle(name) + @prof[name].toggle + end + end + + class ProfilesDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + + def initialize + @profiles = Profiles.new + @active = true + end + + def run + return unless create_dialog + + begin + return event_loop + ensure + close_dialog + end + end
As you can see almost identical code for dialog, so using dialog base really safes you typing and it is usually less buggy, as it is used and tested more heavily.
+ + private + def create_dialog + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + #Header + Heading(_("Profile List")), + # Active profiles + Left( + CheckBox(Id(:active_only), Opt(:notify), _("Show Active only"), @active) + ), + VSpacing(0.4), + # Profile List + table, + VSpacing(0.3), + # Footer buttons + HBox( + HWeight(1, PushButton(Id(:changeMode), _("Change mode"))), + HStretch(), + HWeight(1, PushButton(Id(:finish), Yast::Label.FinishButton)) + ) + ) + ) + end + + def table + headers = Array[_("Name"), _("Mode"), _("PID")] + Table( + Id(:entries_table), + Opt(:keepSorting), + Header(*headers),
nice ;)
+ table_items + ) + end + + def table_items + if @active + profs = @profiles.active + else + profs = @profiles.all + end + arr = Array.new + profs.each do | n, pr | + arr.push(pr.to_array) + end + arr.map { |i| Item(*i) }
I think you can write it as ``` profs.values.map { |i| Item(*i.to_array) } ``` but in email it is not so easy to see content and maybe Profiles provide even nicer api for this.
+ end + + def redraw_table + Yast::UI.ChangeWidget(Id(:entries_table), :Items, table_items) + end + + def event_loop + loop do + case Yast::UI.UserInput + when :active_only + @active = Yast::UI.QueryWidget(Id(:active_only), :Value) + redraw_table + when :cancel + break + when :changeMode + selectedItem = Yast::UI.QueryWidget(Id(:entries_table), :CurrentItem) + log.info "Toggling #{selectedItem}" + @profiles.toggle(selectedItem) + redraw_table
in email indentation is a bit broken, but if you will use rubocop, It will be catched automatic, so I do not need to check it myself.
+ when :finish + break
you can write it together with cancel: ``` when :cancel, :finish break ``` and second part. Problem here is that cancel and finish act same. No difference. I expect users will be confused by it.
+ end + end + end + + def close_dialog + Yast::UI.CloseDialog + end + end +end +
-- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
From: Goldwyn Rodrigues <rgoldwyn@suse.com> The apparmor_ui_dialog communicates with aa-logprof in JSON. Each dialog is converted to a yast dialog for input from the user and returns the actions performed in yast in the form of JSON response. An extra opendialog has been used to indicate the end of the logprof execution. Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor.rb | 1 + src/clients/logprof.rb | 94 +++++++++++++ src/lib/apparmor/apparmor_ui_dialog.rb | 243 +++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 src/clients/logprof.rb create mode 100644 src/lib/apparmor/apparmor_ui_dialog.rb diff --git a/src/clients/apparmor.rb b/src/clients/apparmor.rb index 1e6d7e3..7c2ed6f 100644 --- a/src/clients/apparmor.rb +++ b/src/clients/apparmor.rb @@ -70,6 +70,7 @@ module Yast [ # Selection box items Item(Id("apparmor-settings"), _("Settings"), true), + Item(Id("logprof"), _("Scan Audit logs")), Item(Id("AA_AddProfile"), _("Manually Add Profile")) ] ), diff --git a/src/clients/logprof.rb b/src/clients/logprof.rb new file mode 100644 index 0000000..95cbf5c --- /dev/null +++ b/src/clients/logprof.rb @@ -0,0 +1,94 @@ + +require "open3" +require "json" +require "yast" +require "apparmor/apparmor_ui_dialog" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +module AppArmor + class LogProf + LOGPROF = "/usr/sbin/aa-logprof" + attr_reader :logfile, :raw + include Yast::UIShortcuts + include Yast::Logger + include Yast::I18n + + def initialize(logfile="") + @logfile = logfile + log.info "Logfile is #{logfile}." + end + + def execute() + cmd = "#{LOGPROF} --json" + if @logfile.length > 0 + cmd += " -f #{@logfile}" + end + log.info "Executing #{cmd}" + IO.popen(cmd, "r+") do |f| + f.sync = true + f.each do | line | + log.info "aa-logprof lines #{line}." + if !line.start_with?("{") + next + end + hm = JSON.parse(line) + log.info "aa-logprof hashmap #{hm}." + l = get_dialog(hm) + r = l.run + if !r.nil? + f.puts r.to_json + f.flush + end + end + end + + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(_("No more records in logfile #{@logfile} to process")), + VSpacing(2), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:ok), Yast::Label.OKButton)), + HStretch() + ) + ) + ) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + end + + private + def get_dialog(hm) + case hm["dialog"] + when "yesno" + return YesNoDialog.new(hm) + when "yesnocancel" + return YesNoCancelDialog.new(hm) + when "info" + return InfoDialog.new(hm) + when "getstring" + return GetStringDialog.new(hm) + when "getfile" + return GetFileDialog.new(hm) + when "getstring" + return PromptDialog.new(hm) + when "promptuser" + return PromptDialog.new(hm) + when "apparmor-json-version" + return AAJSONVersion.new(hm) + else + Yast::Report.Error(_("Unknown Dialog %s returned by apparmor") % hm["dialog"]) + return nil + end + end + end +end + +#AppArmor::LogProf.new.execute +AppArmor::LogProf.new("/var/log/audit/audit.log").execute + + diff --git a/src/lib/apparmor/apparmor_ui_dialog.rb b/src/lib/apparmor/apparmor_ui_dialog.rb new file mode 100644 index 0000000..8045301 --- /dev/null +++ b/src/lib/apparmor/apparmor_ui_dialog.rb @@ -0,0 +1,243 @@ +# This file has all the dialog translations/representations +# which come from the apparmor's json +# show_respond() must return what would be communicated back to +# the program in the form of a hash map. It could be nil if no +# data must be written. + +require "yast" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Report" +Yast.import "Popup" + +module AppArmor + class YesNoDialog + include Yast::UIShortcuts + include Yast::I18n + def initialize(hm) + @data = hm["text"] + @map = Hash.new + @map["dialog"] = "yesno" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(@data), + VSpacing(1), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:y), _("Yes"))), + HSpacing(2), + HWeight(1, PushButton(Id(:n), _("No"))), + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + end + Yast::UI.CloseDialog + return @map + end + end + + class YesNoCancelDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + @data = hm["data"] + @map = Hash.new + @map["dialog"] = "yesnocancel" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Label(@data), + VSpacing(0.3), + HBox( + HWeight(1, PushButton(Id(:y), _("Yes"))), + HStretch(), + HWeight(1, PushButton(Id(:n), _("No"))), + HStretch(), + HWeight(1, PushButton(Id(:c), Label.CancelButton)) + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + when :c + @map["response"] = "cancel" + @map["response_key"] = "c" + end + Yast::UI.CloseDialog + return @map + end + end + + class InfoDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + end + def run() + return nil + end + end + + class GetStringDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getstring" + end + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Inputfield(Id(:str), @text), + PushButton("&OK") + ) + ) + str = Yast::UI.QueryWidget(Id(:str), :Value) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + @map["response"] = str + return @map + end + end + + class GetFileDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getfile" + end + def run() + filename = Yast::UI.AskForExistingFile("/etc/apparmor.d", "*", _("Choose a file")) + if !filename.nil? + @map["response"] = filename + end + return @map + end + + end + + class PromptDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + include Yast + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "promptuser" + @title = hm["title"] + @headers = hm["headers"] + @explanation = hm["explanation"] + @options = hm["options"] + @menu_items = hm["menu_items"] + end + def run() + @map["selected"] = 0 + # Display the dynamic widget + + UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + *explanation_label, + *header_labels, + *options_radiobtns, + *menubtns + ) + ) + + @map["response_key"] = Yast::UI.UserInput() + selected = Yast::UI.QueryWidget(:options, :CurrentButton) + @map["selected"] = selected.to_i + return @map + end + private + + def explanation_label + box = VBox() + box[box.size] = VSpacing(1) + if !@explanation.nil? + box[box.size] = Label(@explanation) + end + box + end + + def header_labels + box = VBox() + @headers.each do | key, value | + box[box.size] = Label(key.to_s + ": " + value.to_s) + box[box.size] = VSpacing(1) + end + box + end + + def options_radiobtns + box = VBox() + @options.each_with_index do | opt, i| + box[box.size] = RadioButton(Id(i.to_s), opt, i==0) + box[box.size] = VSpacing(1) + end + VBox(RadioButtonGroup(Id(:options), box)) + end + def menu_to_text_key(menu) + ind = menu.index('(') + key = menu[ind+1, 1].downcase + text = menu.delete('(').delete(')').delete('[').delete(']') + return text, key + end + def menubtns + box = HBox() + @menu_items.each do | menu | + text, key = menu_to_text_key(menu) + box[box.size] = HWeight(1, PushButton(Id(key.to_s), text)) + box[box.size] = HSpacing(1) + end + VBox(box) + end + end + + class AAJSONVersion + include Yast::I18n + include Yast::Logger + AA_JSON = 2.12 + def initialize(hm) + log.info "Hash map #{hm}" + @json_version = hm["data"].to_f + if (@json_version > AA_JSON) + Yast::Report.Error(_("Apparmor JSON version %s is greater than %0.2f" % [@json_version, AA_JSON])) + end + end + def run() + return nil + end + end +end + -- 2.12.3 -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
On Wed, 7 Jun 2017 15:38:47 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
The apparmor_ui_dialog communicates with aa-logprof in JSON. Each dialog is converted to a yast dialog for input from the user and returns the actions performed in yast in the form of JSON response.
An extra opendialog has been used to indicate the end of the logprof execution.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor.rb | 1 + src/clients/logprof.rb | 94 +++++++++++++ src/lib/apparmor/apparmor_ui_dialog.rb | 243 +++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 src/clients/logprof.rb create mode 100644 src/lib/apparmor/apparmor_ui_dialog.rb
diff --git a/src/clients/apparmor.rb b/src/clients/apparmor.rb index 1e6d7e3..7c2ed6f 100644 --- a/src/clients/apparmor.rb +++ b/src/clients/apparmor.rb @@ -70,6 +70,7 @@ module Yast [ # Selection box items Item(Id("apparmor-settings"), _("Settings"), true), + Item(Id("logprof"), _("Scan Audit logs")), Item(Id("AA_AddProfile"), _("Manually Add Profile")) ] ), diff --git a/src/clients/logprof.rb b/src/clients/logprof.rb new file mode 100644 index 0000000..95cbf5c --- /dev/null +++ b/src/clients/logprof.rb @@ -0,0 +1,94 @@ + +require "open3" +require "json" +require "yast" +require "apparmor/apparmor_ui_dialog" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +module AppArmor + class LogProf + LOGPROF = "/usr/sbin/aa-logprof" + attr_reader :logfile, :raw + include Yast::UIShortcuts + include Yast::Logger + include Yast::I18n + + def initialize(logfile="") + @logfile = logfile + log.info "Logfile is #{logfile}." + end + + def execute() + cmd = "#{LOGPROF} --json" + if @logfile.length > 0 + cmd += " -f #{@logfile}" + end + log.info "Executing #{cmd}" + IO.popen(cmd, "r+") do |f| + f.sync = true + f.each do | line | + log.info "aa-logprof lines #{line}." + if !line.start_with?("{") + next + end + hm = JSON.parse(line) + log.info "aa-logprof hashmap #{hm}." + l = get_dialog(hm) + r = l.run + if !r.nil? + f.puts r.to_json + f.flush + end + end + end + + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(_("No more records in logfile #{@logfile} to process")), + VSpacing(2), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:ok), Yast::Label.OKButton)), + HStretch() + ) + ) + ) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + end + + private + def get_dialog(hm) + case hm["dialog"] + when "yesno" + return YesNoDialog.new(hm) + when "yesnocancel" + return YesNoCancelDialog.new(hm) + when "info" + return InfoDialog.new(hm) + when "getstring" + return GetStringDialog.new(hm) + when "getfile" + return GetFileDialog.new(hm) + when "getstring" + return PromptDialog.new(hm) + when "promptuser" + return PromptDialog.new(hm) + when "apparmor-json-version" + return AAJSONVersion.new(hm) + else + Yast::Report.Error(_("Unknown Dialog %s returned by apparmor") % hm["dialog"]) + return nil + end + end + end +end + +#AppArmor::LogProf.new.execute +AppArmor::LogProf.new("/var/log/audit/audit.log").execute + + diff --git a/src/lib/apparmor/apparmor_ui_dialog.rb b/src/lib/apparmor/apparmor_ui_dialog.rb new file mode 100644 index 0000000..8045301 --- /dev/null +++ b/src/lib/apparmor/apparmor_ui_dialog.rb @@ -0,0 +1,243 @@ +# This file has all the dialog translations/representations +# which come from the apparmor's json +# show_respond() must return what would be communicated back to +# the program in the form of a hash map. It could be nil if no +# data must be written. + +require "yast" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Report" +Yast.import "Popup" + +module AppArmor + class YesNoDialog + include Yast::UIShortcuts + include Yast::I18n + def initialize(hm) + @data = hm["text"] + @map = Hash.new + @map["dialog"] = "yesno" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(@data), + VSpacing(1), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:y), _("Yes"))), + HSpacing(2), + HWeight(1, PushButton(Id(:n), _("No"))), + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + end + Yast::UI.CloseDialog + return @map + end + end
how big data you expect? In general maybe easier way is to simply use Yast::Popup.YesNo http://www.rubydoc.info/github/yast/yast-yast2/Yast/PopupClass#YesNo-instanc...
+ + class YesNoCancelDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + @data = hm["data"] + @map = Hash.new + @map["dialog"] = "yesnocancel" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Label(@data), + VSpacing(0.3), + HBox( + HWeight(1, PushButton(Id(:y), _("Yes"))), + HStretch(), + HWeight(1, PushButton(Id(:n), _("No"))), + HStretch(), + HWeight(1, PushButton(Id(:c), Label.CancelButton)) + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + when :c + @map["response"] = "cancel" + @map["response_key"] = "c" + end + Yast::UI.CloseDialog + return @map + end + end
Here I would reuse Popup.AnyQuestion3 http://www.rubydoc.info/github/yast/yast-yast2/Yast%2FPopupClass:AnyQuestion...
+ + class InfoDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + end + def run() + return nil + end + end
Ugh, what? what is purpose of this dialog? It do nothing.
+ + class GetStringDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getstring" + end + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Inputfield(Id(:str), @text), + PushButton("&OK") + ) + ) + str = Yast::UI.QueryWidget(Id(:str), :Value) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + @map["response"] = str + return @map + end + end + + class GetFileDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getfile" + end + def run() + filename = Yast::UI.AskForExistingFile("/etc/apparmor.d", "*", _("Choose a file")) + if !filename.nil? + @map["response"] = filename + end + return @map + end + + end + + class PromptDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + include Yast + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "promptuser" + @title = hm["title"] + @headers = hm["headers"] + @explanation = hm["explanation"] + @options = hm["options"] + @menu_items = hm["menu_items"] + end + def run() + @map["selected"] = 0 + # Display the dynamic widget + + UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + *explanation_label, + *header_labels, + *options_radiobtns, + *menubtns + ) + ) + + @map["response_key"] = Yast::UI.UserInput() + selected = Yast::UI.QueryWidget(:options, :CurrentButton) + @map["selected"] = selected.to_i + return @map + end + private + + def explanation_label + box = VBox() + box[box.size] = VSpacing(1)
term have << method http://www.rubydoc.info/github/yast/yast-ruby-bindings/Yast/Term#%3C%3C-inst... but here. I suggest to simply have ``` box = VBox(VSpacing(1)) ```
+ if !@explanation.nil? + box[box.size] = Label(@explanation) and here
box << Label(@explanation) if @explanation
+ end + box + end + + def + box = VBox() + @headers.each do | key, value | + box[box.size] = Label(key.to_s + ": " + value.to_s) + box[box.size] = VSpacing(1)
same here. << operator is easier to read.
+ end + box
btw whole method can be done using each_with_object like ``` def header_labels @headers.each_with_object(VBox()) do |pair, result| key, value = pair result << Label(key.to_s + ": " + value.to_s) result << VSpacing(1) end end ```
+ end + + def options_radiobtns
typo here
+ box = VBox() + @options.each_with_index do | opt, i| + box[box.size] = RadioButton(Id(i.to_s), opt, i==0) + box[box.size] = VSpacing(1) + end + VBox(RadioButtonGroup(Id(:options), box)) + end + def menu_to_text_key(menu) + ind = menu.index('(') + key = menu[ind+1, 1].downcase + text = menu.delete('(').delete(')').delete('[').delete(']') + return text, key + end + def menubtns + box = HBox() + @menu_items.each do | menu | + text, key = menu_to_text_key(menu) + box[box.size] = HWeight(1, PushButton(Id(key.to_s), text)) + box[box.size] = HSpacing(1) + end + VBox(box) + end + end + + class AAJSONVersion + include Yast::I18n + include Yast::Logger + AA_JSON = 2.12
I expect this will break often in future. Is there any plans to have json somehow backward compatible?
+ def initialize(hm) + log.info "Hash map #{hm}" + @json_version = hm["data"].to_f + if (@json_version > AA_JSON) + Yast::Report.Error(_("Apparmor JSON version %s is greater than %0.2f" % [@json_version, AA_JSON])) + end + end + def run() + return nil + end + end +end +
And that is all, a lot of code, some parts looks really interesting. Good that you send it for early review. Do not hesitate to contact us on IRC if you have any questions. Josef -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
Hello, Am Donnerstag, 8. Juni 2017, 10:06:52 CEST schrieb Josef Reidinger:
On Wed, 7 Jun 2017 15:38:47 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
The apparmor_ui_dialog communicates with aa-logprof in JSON. Each dialog is converted to a yast dialog for input from the user and returns the actions performed in yast in the form of JSON response.
An extra opendialog has been used to indicate the end of the logprof execution.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com>
...
+module AppArmor + class YesNoDialog + include Yast::UIShortcuts + include Yast::I18n + def initialize(hm) + @data = hm["text"] + @map = Hash.new + @map["dialog"] = "yesno" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(@data), + VSpacing(1), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:y), _("Yes"))), + HSpacing(2), + HWeight(1, PushButton(Id(:n), _("No"))), + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + end + Yast::UI.CloseDialog + return @map + end + end
how big data you expect? In general maybe easier way is to simply use Yast::Popup.YesNo http://www.rubydoc.info/github/yast/yast-yast2/Yast/PopupClass#YesNo-> instance_method
Indeed, there's no need to re-invent the wheel ;-)
+ class YesNoCancelDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + @data = hm["data"] + @map = Hash.new + @map["dialog"] = "yesnocancel" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Label(@data), + VSpacing(0.3), + HBox( + HWeight(1, PushButton(Id(:y), _("Yes"))), + HStretch(), + HWeight(1, PushButton(Id(:n), _("No"))), + HStretch(), + HWeight(1, PushButton(Id(:c), Label.CancelButton)) + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + when :c + @map["response"] = "cancel" + @map["response_key"] = "c" + end + Yast::UI.CloseDialog + return @map + end + end
Here I would reuse Popup.AnyQuestion3 http://www.rubydoc.info/github/yast/yast-yast2/Yast%2FPopupClass:AnyQu estion3
Another wheel that doesn't need re-invention.
+ class InfoDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + end + def run() + return nil + end + end
Ugh, what? what is purpose of this dialog? It do nothing.
I see a "log.info" call there, so I'd guess (without knowing what log.info does exactly) it writes #{hm} to the YaST log. FYI: messages on the info level are something like - "/foo/* r," added to the profile - Deleted 2 previous matching profile entries. While the text version of aa-logprof displays those messages, they are not too important - and especially not important enough for displaying a popup dialog. Writing them to the log and hiding them in the user interface looks like a valid option to me. ...
+ class AAJSONVersion + include Yast::I18n + include Yast::Logger + AA_JSON = 2.12
I expect this will break often in future. Is there any plans to have json somehow backward compatible?
I don't really expect changes ;-) The json is constructed in a way that allows lots of flexibilty - for example, adding support for new rule types in the AppArmor tools will _not_ require any changes on the YaST side and things will just work. Basically the json describes the dialog layout (header, available options, available buttons etc.) and YaST will "just" be the frontend. The only case that will require changes in the json layout is adding a new dialog type - for example a new variant of the yes/no dialog. I tried to come up with a possible example, but didn't find one, so it's unlikely that this happens in the near future. That said - I'll happily accept some tests in the upstream AppArmor test suite that ensure we don't accidently break the json interface. Such a test would talk to aa-logprof --json (like YaST will do) and could include - some example profiles (or just use the profiles we ship anyway) - 20 lines of audit.log events - a json "script" describing which messages/questions to expect from aa-logprof and how to answer them (one per line, prefixed by IN or OUT or something like that) - the expected profiles after the aa-logprof run Regards, Christian Boltz -- <mancha> who forgot to load the troll profile? [from #apparmor] -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
participants (4)
-
Ancor Gonzalez Sosa
-
Christian Boltz
-
Goldwyn Rodrigues
-
Josef Reidinger