train-kubernetes 0.1.7 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -0
- data/lib/train-kubernetes/connection.rb +14 -5
- data/lib/train-kubernetes/file/linux.rb +149 -0
- data/lib/train-kubernetes/file/linux_immutable_file_check.rb +50 -0
- data/lib/train-kubernetes/file/linux_permissions.rb +25 -0
- data/lib/train-kubernetes/kubectl_client.rb +17 -7
- data/lib/train-kubernetes/transport.rb +3 -0
- data/lib/train-kubernetes/version.rb +1 -1
- data/lib/train-kubernetes.rb +1 -0
- data/train-kubernetes.gemspec +2 -1
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcca2f4cc7de5b099cb5a3ee64e8ec7e935fdb986234d4ca9a3659d73efa874e
|
4
|
+
data.tar.gz: 15006a25d2cc0a823e21025d0b9f02cdd75bcefcac6965a28b17735147c1a4df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17df60f7afdf0948ea15ce899b97b3aa33ba2173e87715c2b736783644a48aae037112188c570aab65f2cc3f40978e16c43c716c489623a7f556757b9204bfa6
|
7
|
+
data.tar.gz: 47f38cc6b1b41873f411da7a6fc3be723d2ead3cf5a0b225367a5bd6bfe518a57eb06f1805e1557d708d8e3c161881a4b4dbc6747aaaf0e760573795c5a43f38
|
data/README.md
CHANGED
@@ -28,6 +28,13 @@ describe k8sobject(api: 'v1', type: 'pod', namespace: 'default', name: 'my-pod')
|
|
28
28
|
end
|
29
29
|
```
|
30
30
|
|
31
|
+
## File resource
|
32
|
+
```ruby
|
33
|
+
inspec.backend.file('PATH', pod: 'POD', container: 'CONTAINER', namespace: 'NAMESPACE')
|
34
|
+
```
|
35
|
+
Currently it supports only Linux based containers
|
36
|
+
|
37
|
+
|
31
38
|
## Preconditions
|
32
39
|
|
33
40
|
- InSpec 3.7+ or 4.x+
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'train'
|
2
2
|
require 'k8s-ruby'
|
3
3
|
require 'train-kubernetes/platform'
|
4
|
+
require 'train-kubernetes/kubectl_client'
|
4
5
|
|
5
6
|
module TrainPlugins
|
6
7
|
module TrainKubernetes
|
@@ -9,8 +10,9 @@ module TrainPlugins
|
|
9
10
|
|
10
11
|
def initialize(options)
|
11
12
|
super(options)
|
12
|
-
@pod = options[:pod]
|
13
|
+
@pod = options[:pod] || options[:path]&.gsub('/', '')
|
13
14
|
@container = options[:container]
|
15
|
+
@namespace = options[:namespace] || options[:host]
|
14
16
|
parse_kubeconfig
|
15
17
|
connect
|
16
18
|
end
|
@@ -34,15 +36,22 @@ module TrainPlugins
|
|
34
36
|
|
35
37
|
def parse_kubeconfig
|
36
38
|
kubeconfig_file = @options[:kubeconfig] if @options[:kubeconfig]
|
37
|
-
@client = K8s::Client.config(K8s::Config.load_file(File.expand_path(kubeconfig_file)))
|
39
|
+
@client = K8s::Client.config(K8s::Config.load_file(::File.expand_path(kubeconfig_file)))
|
38
40
|
end
|
39
41
|
|
40
42
|
private
|
41
43
|
|
42
|
-
attr_reader :pod, :container
|
44
|
+
attr_reader :pod, :container, :namespace
|
43
45
|
|
44
|
-
def run_command_via_connection(cmd, &_data_handler)
|
45
|
-
KubectlClient.new(pod: pod
|
46
|
+
def run_command_via_connection(cmd, opts = {}, &_data_handler)
|
47
|
+
KubectlClient.new(pod: opts[:pod] || pod,
|
48
|
+
container: opts[:container] || container,
|
49
|
+
namespace: opts[:namespace] || namespace)
|
50
|
+
.execute(cmd)
|
51
|
+
end
|
52
|
+
|
53
|
+
def file_via_connection(path, **args)
|
54
|
+
TrainPlugins::TrainKubernetes::File::Linux.new(self, path, pod: args[:pod], container: args[:container], namespace: args[:namespace])
|
46
55
|
end
|
47
56
|
end
|
48
57
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'train/file/remote/linux'
|
2
|
+
require 'train/extras/stat'
|
3
|
+
|
4
|
+
module TrainPlugins
|
5
|
+
module TrainKubernetes
|
6
|
+
module File
|
7
|
+
class Linux < ::Train::File::Remote::Linux
|
8
|
+
def initialize(backend, path, follow_symlink = true, pod:, **args)
|
9
|
+
@backend = backend
|
10
|
+
@path = path || ''
|
11
|
+
@follow_symlink = follow_symlink
|
12
|
+
@pod = pod
|
13
|
+
@container = args[:container]
|
14
|
+
@namespace = args[:namespace]
|
15
|
+
|
16
|
+
sanitize_filename(path)
|
17
|
+
super(backend, path, follow_symlink)
|
18
|
+
end
|
19
|
+
|
20
|
+
def content
|
21
|
+
return @content if defined?(@content)
|
22
|
+
|
23
|
+
@content = @backend.run_command("cat #{@spath} || echo -n",
|
24
|
+
{ pod: pod, namespace: namespace, container: container })
|
25
|
+
.stdout
|
26
|
+
return @content unless @content.empty?
|
27
|
+
|
28
|
+
@content = nil if directory? || size.nil? || (size > 0)
|
29
|
+
@content
|
30
|
+
end
|
31
|
+
|
32
|
+
def content=(new_content)
|
33
|
+
execute_result = @backend.run_command('base64 --help', { pod: pod, namespace: namespace, container: container })
|
34
|
+
if execute_result.exit_status != 0
|
35
|
+
raise TransportError, "#{self.class} found no base64 binary for file writes"
|
36
|
+
end
|
37
|
+
|
38
|
+
unix_cmd = format("echo '%<base64>s' | base64 --decode > %<file>s",
|
39
|
+
base64: Base64.strict_encode64(new_content),
|
40
|
+
file: @spath)
|
41
|
+
|
42
|
+
@backend.run_command(unix_cmd, { pod: pod, namespace: namespace, container: container })
|
43
|
+
|
44
|
+
@content = new_content
|
45
|
+
end
|
46
|
+
|
47
|
+
def exist?
|
48
|
+
@exist ||= begin
|
49
|
+
f = @follow_symlink ? '' : " || test -L #{@spath}"
|
50
|
+
@backend.run_command("test -e #{@spath}" + f,
|
51
|
+
{ pod: pod, namespace: namespace, container: container })
|
52
|
+
.exit_status == 0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def mounted
|
57
|
+
@mounted ||=
|
58
|
+
@backend.run_command("mount | grep -- ' on #{@path} '",
|
59
|
+
{ pod: pod, namespace: namespace, container: container })
|
60
|
+
end
|
61
|
+
|
62
|
+
def path
|
63
|
+
return @path unless @follow_symlink && symlink?
|
64
|
+
|
65
|
+
@link_path ||= read_target_path
|
66
|
+
end
|
67
|
+
|
68
|
+
def shallow_link_path
|
69
|
+
return nil unless symlink?
|
70
|
+
|
71
|
+
@shallow_link_path ||=
|
72
|
+
@backend.run_command("readlink #{@spath}", { pod: pod, namespace: namespace, container: container })
|
73
|
+
.stdout
|
74
|
+
.chomp
|
75
|
+
end
|
76
|
+
|
77
|
+
def stat
|
78
|
+
@stat ||= begin
|
79
|
+
shell_escaped_path = @spath
|
80
|
+
backend = @backend
|
81
|
+
follow_symlink = @follow_symlink
|
82
|
+
lstat = follow_symlink ? ' -L' : ''
|
83
|
+
format = '--printf'
|
84
|
+
res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null #{format} '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'",
|
85
|
+
{ pod: pod, namespace: namespace, container: container })
|
86
|
+
# ignore the exit_code: it is != 0 if selinux labels are not supported
|
87
|
+
# on the system.
|
88
|
+
|
89
|
+
fields = res.stdout.split("\n")
|
90
|
+
return {} if fields.length != 9
|
91
|
+
|
92
|
+
tmask = fields[1].to_i(16)
|
93
|
+
selinux = fields[8]
|
94
|
+
## selinux security context string not available on esxi
|
95
|
+
selinux = nil if (selinux == '?') || (selinux == '(null)') || (selinux == 'C')
|
96
|
+
{
|
97
|
+
type: Train::Extras::Stat.find_type(tmask),
|
98
|
+
mode: tmask & 07777,
|
99
|
+
owner: fields[2],
|
100
|
+
uid: fields[3].to_i,
|
101
|
+
group: fields[4],
|
102
|
+
gid: fields[5].to_i,
|
103
|
+
mtime: fields[7].to_i,
|
104
|
+
size: fields[0].to_i,
|
105
|
+
selinux_label: selinux
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def source
|
111
|
+
if @follow_symlink
|
112
|
+
self.class.new(@backend, @path, false, pod: pod, container: container, namespace: namespace)
|
113
|
+
else
|
114
|
+
self
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def user_permissions
|
119
|
+
return {} unless exist?
|
120
|
+
|
121
|
+
skip_reource '`user_permissions` is not supported on your Linux Containers yet.'
|
122
|
+
end
|
123
|
+
|
124
|
+
def inherited?
|
125
|
+
return false unless exist?
|
126
|
+
|
127
|
+
skip_resource '`inherited?` is not supported on your Linux Containers yet.'
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Returns full path of a symlink target(real dest) or '' on symlink loop
|
133
|
+
def read_target_path
|
134
|
+
full_path = @backend.run_command("readlink -n #{@spath} -f",
|
135
|
+
{ pod: pod, namespace: namespace, container: container })
|
136
|
+
.stdout
|
137
|
+
# Needed for some OSes like OSX that returns relative path
|
138
|
+
# when the link and target are in the same directory
|
139
|
+
if !full_path.start_with?('/') && full_path != ''
|
140
|
+
full_path = ::File.expand_path("../#{full_path}", @spath)
|
141
|
+
end
|
142
|
+
full_path
|
143
|
+
end
|
144
|
+
|
145
|
+
attr_reader :pod, :container, :namespace
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'inspec/resources/file'
|
2
|
+
require 'inspec/exceptions'
|
3
|
+
|
4
|
+
module TrainPlugins
|
5
|
+
module TrainKubernetes
|
6
|
+
module File
|
7
|
+
class LinuxImmutableFileCheck < Inspec::Resources::LinuxImmutableFlagCheck
|
8
|
+
def initialize(inspec, file, pod:, container: nil, namespace: nil)
|
9
|
+
@pod = pod
|
10
|
+
@container = container
|
11
|
+
@namespace = namespace
|
12
|
+
super(inspec, file)
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_utility_or_error(utility_name)
|
16
|
+
%W(/usr/sbin/#{utility_name} /sbin/#{utility_name} /usr/bin/#{utility_name} /bin/#{utility_name} #{utility_name}).each do |cmd|
|
17
|
+
if inspec.backend
|
18
|
+
.run_command("sh -c 'type \"#{cmd}\"'", { pod: pod, container: container, namespace: namespace })
|
19
|
+
.exit_status.to_i == 0
|
20
|
+
return cmd
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_immutable?
|
28
|
+
# Check if lsattr is available. In general, all linux system has lsattr & chattr
|
29
|
+
# This logic check is valid for immutable flag set with chattr
|
30
|
+
utility = find_utility_or_error('lsattr')
|
31
|
+
|
32
|
+
utility_cmd = inspec.backend.run_command("#{utility} #{file_path}",
|
33
|
+
{ pod: pod, container: container, namespace: namespace })
|
34
|
+
|
35
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
|
36
|
+
|
37
|
+
# General output for lsattr file_name is:
|
38
|
+
# ----i---------e----- file_name
|
39
|
+
# The fifth char resembles the immutable flag. Total 20 flags are allowed.
|
40
|
+
lsattr_info = utility_cmd.stdout.strip.squeeze(' ')
|
41
|
+
lsattr_info =~ /^.{4}i.{15} .*/
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :pod, :container, :namespace
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TrainPlugins
|
2
|
+
module TrainKubernetes
|
3
|
+
module File
|
4
|
+
class LinuxPermissions < Inspec::Resources::UnixFilePermissions
|
5
|
+
def initialize(inspec, pod:, namespace: nil, container: nil)
|
6
|
+
@pod = pod
|
7
|
+
@namespace = namespace
|
8
|
+
@container = container
|
9
|
+
super(inspec)
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_file_permission_by_user(access_type, user, path)
|
13
|
+
flag = permission_flag(access_type)
|
14
|
+
perm_cmd = "su -s /bin/sh -c \"test -#{flag} #{path}\" #{user}"
|
15
|
+
cmd = inspec.backend.run_command(perm_cmd, { pod: pod, namespace: namespace, container: container })
|
16
|
+
cmd.exit_status == 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :pod, :container, :namespace
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,18 +1,26 @@
|
|
1
1
|
require 'mixlib/shellout'
|
2
|
+
require 'train/extras'
|
2
3
|
|
3
4
|
module TrainPlugins
|
4
5
|
module TrainKubernetes
|
5
6
|
class KubectlClient
|
6
|
-
attr_reader :pod, :container
|
7
|
-
|
7
|
+
attr_reader :pod, :container, :namespace
|
8
|
+
|
9
|
+
DEFAULT_NAMESPACE = 'default'.freeze
|
10
|
+
|
11
|
+
def initialize(pod:, namespace: nil, container: nil)
|
8
12
|
@pod = pod
|
9
13
|
@container = container
|
14
|
+
@namespace = namespace || DEFAULT_NAMESPACE
|
10
15
|
end
|
11
16
|
|
12
17
|
def execute(command, stdin: true, tty: true)
|
13
18
|
instruction = build_instruction(command, stdin, tty)
|
14
19
|
shell = Mixlib::ShellOut.new(instruction)
|
15
|
-
shell.run_command
|
20
|
+
res = shell.run_command
|
21
|
+
Train::Extras::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
|
22
|
+
rescue Errno::ENOENT => _e
|
23
|
+
Train::Extras::CommandResult.new('', '', 1)
|
16
24
|
end
|
17
25
|
|
18
26
|
private
|
@@ -21,14 +29,16 @@ module TrainPlugins
|
|
21
29
|
@shell ||= Mixlib::ShellOut.new(instruction)
|
22
30
|
end
|
23
31
|
|
24
|
-
def build_instruction(command, stdin,
|
25
|
-
[
|
32
|
+
def build_instruction(command, stdin, _tty)
|
33
|
+
['kubectl exec'].tap do |arr|
|
26
34
|
arr << '--stdin' if stdin
|
27
|
-
arr << "--tty" if tty
|
28
35
|
arr << pod if pod
|
36
|
+
arr << '-n'
|
37
|
+
arr << namespace
|
38
|
+
arr << '--'
|
29
39
|
arr << command
|
30
40
|
end.join("\s")
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
34
|
-
end
|
44
|
+
end
|
@@ -5,6 +5,9 @@ module TrainPlugins
|
|
5
5
|
class Transport < Train.plugin(1)
|
6
6
|
name 'k8s'
|
7
7
|
option :kubeconfig, default: ENV['KUBECONFIG'] || '~/.kube/config'
|
8
|
+
option :pod, default: nil
|
9
|
+
option :container, default: nil
|
10
|
+
option :namespace, default: nil
|
8
11
|
def connection(_instance_opts = nil)
|
9
12
|
@connection ||= TrainPlugins::TrainKubernetes::Connection.new(@options)
|
10
13
|
end
|
data/lib/train-kubernetes.rb
CHANGED
data/train-kubernetes.gemspec
CHANGED
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
|
|
44
44
|
# Do not list inspec as a dependency of the train plugin.
|
45
45
|
|
46
46
|
# All plugins should mention train, > 1.4
|
47
|
-
|
47
|
+
# pinning k8s-ruby to 0.10.5 to avoid broken dry-type gem upgrades from k8s-ruby
|
48
|
+
spec.add_dependency 'k8s-ruby', '~> 0.14.0'
|
48
49
|
spec.add_dependency 'train', '~> 3.0'
|
49
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-kubernetes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Geesaman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: k8s-ruby
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.14.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.14.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: train
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,6 +50,9 @@ files:
|
|
50
50
|
- README.md
|
51
51
|
- lib/train-kubernetes.rb
|
52
52
|
- lib/train-kubernetes/connection.rb
|
53
|
+
- lib/train-kubernetes/file/linux.rb
|
54
|
+
- lib/train-kubernetes/file/linux_immutable_file_check.rb
|
55
|
+
- lib/train-kubernetes/file/linux_permissions.rb
|
53
56
|
- lib/train-kubernetes/kubectl_client.rb
|
54
57
|
- lib/train-kubernetes/platform.rb
|
55
58
|
- lib/train-kubernetes/transport.rb
|
@@ -74,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
77
|
- !ruby/object:Gem::Version
|
75
78
|
version: '0'
|
76
79
|
requirements: []
|
77
|
-
rubygems_version: 3.
|
80
|
+
rubygems_version: 3.3.7
|
78
81
|
signing_key:
|
79
82
|
specification_version: 4
|
80
83
|
summary: Train Kubernetes
|