Class: OsCtld::AppArmor

Inherits:
Object
  • Object
show all
Extended by:
OsCtl::Lib::Utils::System
Includes:
OsCtl::Lib::Utils::Log, OsCtl::Lib::Utils::System
Defined in:
lib/osctld/apparmor.rb

Constant Summary collapse

PATHS =

Paths where ‘apparmor_parser` searches for configuration files

[RunState::APPARMOR_DIR].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ct) ⇒ AppArmor

Returns a new instance of AppArmor.

Parameters:



105
106
107
# File 'lib/osctld/apparmor.rb', line 105

def initialize(ct)
  @ct = ct
end

Instance Attribute Details

#ctObject (readonly, protected)

Returns the value of attribute ct.



193
194
195
# File 'lib/osctld/apparmor.rb', line 193

def ct
  @ct
end

Class Method Details

.apparmor_parser(pool, cmd, profiles, opts = {}) ⇒ Object

Call apparmor_parser

Parameters:

  • pool (Pool)
  • cmd ("a", "r", "R")
  • profiles (Array<String>)

    absolute paths to profiles

  • opts (Hash) (defaults to: {})

    options for ‘syscmd`



96
97
98
99
100
101
102
# File 'lib/osctld/apparmor.rb', line 96

def self.apparmor_parser(pool, cmd, profiles, opts = {})
  syscmd(
    "apparmor_parser -#{cmd} -W -v #{PATHS.map { |v| "-I #{v}" }.join(' ')} " \
    "-L #{cache_dir(pool)} #{profiles.join(' ')}",
    opts
  )
end

.assets(add, pool) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/osctld/apparmor.rb', line 74

def self.assets(add, pool)
  add.directory(
    profile_dir(pool),
    desc: 'Per-container AppArmor profiles',
    user: 0,
    group: 0,
    mode: 0o700
  )
  add.directory(
    profile_dir(pool),
    desc: 'Cache for apparmor_parser',
    user: 0,
    group: 0,
    mode: 0o700
  )
end

.cache_dir(pool) ⇒ Object

Per-pool runstate directory with profile cache



70
71
72
# File 'lib/osctld/apparmor.rb', line 70

def self.cache_dir(pool)
  File.join(pool.apparmor_dir, 'cache')
end

.enabled?Boolean

Returns:

  • (Boolean)


13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/osctld/apparmor.rb', line 13

def self.enabled?
  if @enabled.nil?
    begin
      @enabled = Daemon.get.config.apparmor_paths.any? \
        && Dir.exist?('/sys/kernel/security/apparmor') \
        && File.read('/sys/module/apparmor/parameters/enabled').strip.downcase == 'y'
    rescue Errno::ENOENT
      @enabled = false
    end
  else
    @enabled
  end
end

.profile_dir(pool) ⇒ Object

Per-pool runstate directory with profiles



65
66
67
# File 'lib/osctld/apparmor.rb', line 65

def self.profile_dir(pool)
  File.join(pool.apparmor_dir, 'profiles')
end

.setupObject

Prepare shared files in ‘/run/osctl`



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/osctld/apparmor.rb', line 28

def self.setup
  PATHS.concat(Daemon.get.config.apparmor_paths)

  base = File.join(RunState::APPARMOR_DIR, 'osctl')
  features = File.join(base, 'features')

  [base, features].each do |dir|
    FileUtils.mkdir_p(dir, mode: 0o755)
  end

  ErbTemplate.render_to(
    'apparmor/features/nesting',
    {},
    File.join(features, 'nesting')
  )
end

.setup_pool(pool) ⇒ Object

Load profiles of running containers from ‘pool`

Parameters:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/osctld/apparmor.rb', line 47

def self.setup_pool(pool)
  [profile_dir(pool), cache_dir(pool)].each do |dir|
    FileUtils.mkdir_p(dir, mode: 0o700)
  end

  cts = DB::Containers.get.select do |ct|
    next(false) if ct.pool != pool || !ct.running?

    ct.apparmor.generate_profile
    true
  end

  return unless cts.any?

  apparmor_parser(pool, 'r', cts.map { |ct| ct.apparmor.profile_path })
end

Instance Method Details

#apparmor_parser(cmd, opts = {}) ⇒ Object (protected)



203
204
205
# File 'lib/osctld/apparmor.rb', line 203

def apparmor_parser(cmd, opts = {})
  self.class.apparmor_parser(ct.pool, cmd, [profile_path], opts)
end

#cache_dirObject (protected)



199
200
201
# File 'lib/osctld/apparmor.rb', line 199

def cache_dir
  self.class.cache_dir(ct.pool)
end

#create_namespaceObject

Create an AppArmor namespace for the container



156
157
158
159
# File 'lib/osctld/apparmor.rb', line 156

def create_namespace
  path = namespace_path
  FileUtils.mkdir_p(path)
end

#destroy_namespaceObject

Destroy the container’s AppArmor namespace



162
163
164
165
# File 'lib/osctld/apparmor.rb', line 162

def destroy_namespace
  path = namespace_path
  FileUtils.rm_f(path)
end

#destroy_profileObject

Remove the container’s profile from the kernel and remove it from cache



144
145
146
147
148
149
150
151
152
153
# File 'lib/osctld/apparmor.rb', line 144

def destroy_profile
  unload_profile if File.exist?(profile_path)

  begin
    cached = File.join(cache_dir, profile_name)
    File.unlink(cached)
  rescue Errno::ENOENT
    # ignore
  end
end

#dup(new_ct) ⇒ Object



185
186
187
188
189
# File 'lib/osctld/apparmor.rb', line 185

def dup(new_ct)
  ret = super()
  ret.instance_variable_set('@ct', new_ct)
  ret
end

#generate_profileObject

Generate AppArmor profile for the container

The profile is generated only if it has been changed to let ‘apparmor_parser` use cached profiles for faster container startup times.



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/osctld/apparmor.rb', line 120

def generate_profile
  ErbTemplate.render_to_if_changed('apparmor/profile', {
    name: profile_name,
    namespace:,
    ct:,
    all_combinations_of: lambda do |arr|
      ret = []
      arr.count.times { |i| ret.concat(arr.combination(i + 1).to_a) }
      ret
    end
  }, profile_path)
end

#load_profileObject

Load the container’s profile into the kernel



134
135
136
# File 'lib/osctld/apparmor.rb', line 134

def load_profile
  apparmor_parser('r')
end

#namespaceObject



175
176
177
178
179
# File 'lib/osctld/apparmor.rb', line 175

def namespace
  # Ubuntu's AppArmor service initializes profiles only when in a namespace
  # beginning with `lxd-` or `lxc-`, so we have to use the prefix as well.
  "lxc-#{profile_name}"
end

#namespace_pathObject (protected)



195
196
197
# File 'lib/osctld/apparmor.rb', line 195

def namespace_path
  File.join('/sys/kernel/security/apparmor/policy/namespaces', namespace)
end

#namespace_profile_nameObject



181
182
183
# File 'lib/osctld/apparmor.rb', line 181

def namespace_profile_name
  "#{profile_name}//&:#{namespace}:"
end

#profile_nameObject



167
168
169
# File 'lib/osctld/apparmor.rb', line 167

def profile_name
  "ct-#{ct.pool.name}-#{ct.id}"
end

#profile_pathObject



171
172
173
# File 'lib/osctld/apparmor.rb', line 171

def profile_path
  File.join(self.class.profile_dir(ct.pool), profile_name)
end

#setupObject

Generate container profile, load it and create a namespace



110
111
112
113
114
# File 'lib/osctld/apparmor.rb', line 110

def setup
  generate_profile
  load_profile
  create_namespace
end

#unload_profileObject

Remove the container’s profile from the kernel



139
140
141
# File 'lib/osctld/apparmor.rb', line 139

def unload_profile
  apparmor_parser('R', valid_rcs: [254])
end