pipedawg 0.3.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 041e80c36e4a1f30695c0d60233c8fcc3261ac734dee7d0770dde11ad1e0ac71
4
- data.tar.gz: ee48577c9c00e3ef4f76cabe22895d435b26bb10cd6dd4ff5a950dbaebaaf5bc
3
+ metadata.gz: b2e0ba1c718b0efe498f222720490aeb2566a6f5c4e6fb1f0b56e5e9874669a5
4
+ data.tar.gz: 5545deb6f783e71bb5b35bb6633aed7939a9ac0e74aefb50200020976c1b6489
5
5
  SHA512:
6
- metadata.gz: 83e2ef040fbdbec747968ca61d58079e720dd32cea77fd0dc0b39f451dab618e247cf85617f6f6f6dbaf881263e90c4b2f7b90b3df96d5c60e6a338db5b007d1
7
- data.tar.gz: 34cccd812da30055338afc7368a5f62f46c0df7ba8e7dc7c21e9590fdcd4a5d398df30be22f0e5aac6c7a9a3944f4683b1b96be79da9b75276aca89ee13e8d89
6
+ metadata.gz: 17fbfd9bb667f808c44e636d7133fa379d115b650102162d35b5e0fd7b6594466493663c7193ef8092d3e55c2480737bbf4e4bf5923260096ae7706200320d04
7
+ data.tar.gz: 338192888145922229beba1e38fa3794e2f1d7d4e197987de1b5ae7425df29f2239f47a011846b10df20723d43c93a26d26813755a33d2b193780b0812429f74
data/README.md CHANGED
@@ -42,13 +42,13 @@ gem_job = Pipedawg::Job.new(
42
42
  script: ['bundle install', 'gem build *.gemspec']
43
43
  )
44
44
 
45
- kaniko_job = Pipedawg::KanikoJob.new(
45
+ kaniko_build_job = Pipedawg::KanikoBuildJob.new(
46
46
  'build:kaniko',
47
47
  {needs: ['build:gem'], retry: 2},
48
48
  {context:'${CI_PROJECT_DIR}/docker',external_files: {'*.gem':'gems'}}
49
49
  )
50
50
 
51
- pipeline = Pipedawg::Pipeline.new 'build:image', jobs: [gem_job, kaniko_job]
51
+ pipeline = Pipedawg::Pipeline.new 'build:image', jobs: [gem_job, kaniko_build_job]
52
52
  puts pipeline.to_yaml
53
53
  ```
54
54
 
@@ -7,7 +7,7 @@ module Pipedawg
7
7
 
8
8
  def initialize(name = 'build', opts = {}, helm_opts = {})
9
9
  @helm_opts = {
10
- chart: name,
10
+ chart: name, debug: true,
11
11
  destinations: [{ user: nil, password: nil, url: nil }],
12
12
  helm: 'helm',
13
13
  image: { entrypoint: [''], name: 'alpine/helm' },
@@ -17,13 +17,21 @@ module Pipedawg
17
17
  update
18
18
  end
19
19
 
20
- def update
20
+ def update # rubocop:disable Metrics/AbcSize
21
21
  opts[:image] = helm_opts[:image] if helm_opts[:image]
22
- opts[:script] = [] + pull + (helm_opts[:destinations].map { |d| push(d) }).flatten(1)
22
+ opts[:script] = debug + pull + (helm_opts[:destinations].map { |d| push(d) }).flatten(1)
23
23
  end
24
24
 
25
25
  private
26
26
 
27
+ def debug
28
+ if helm_opts[:debug]
29
+ Pipedawg::Util.echo_proxy_vars
30
+ else
31
+ []
32
+ end
33
+ end
34
+
27
35
  def pull
28
36
  case helm_opts[:url]
29
37
  when nil
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pipedawg
4
- # kaniko_job class
5
- class KanikoJob < Job
4
+ # kaniko_build_job class
5
+ class KanikoBuildJob < Job
6
6
  attr_accessor :kaniko_opts
7
7
 
8
8
  def initialize(name = 'build', opts = {}, kaniko_opts = {}) # rubocop:disable Metrics/MethodLength
@@ -11,13 +11,12 @@ module Pipedawg
11
11
  config: {
12
12
  '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' }
13
13
  },
14
- config_file: '/kaniko/.docker/config.json', context: '${CI_PROJECT_DIR}',
15
- destinations: [], dockerfile: 'Dockerfile', executor: '/kaniko/executor',
16
- external_files: {}, flags: [], ignore_paths: [], insecure_registries: [],
17
- image: { entrypoint: [''], name: 'gcr.io/kaniko-project/executor:debug' },
18
- options: {}, registry_certificates: {}, registry_mirrors: [],
19
- skip_tls_verify_registry: [], trusted_ca_cert_source_files: [],
20
- trusted_ca_cert_target_file: '/kaniko/ssl/certs/ca-certificates.crt'
14
+ config_file: '/kaniko/.docker/config.json', context: '${CI_PROJECT_DIR}', debug: true,
15
+ destinations: [], dockerfile: 'Dockerfile', executor: '/kaniko/executor', external_files: {},
16
+ flags: [], ignore_paths: [], insecure_registries: [],
17
+ image: { entrypoint: [''], name: 'gcr.io/kaniko-project/executor:debug' }, options: {},
18
+ registry_certificates: {}, registry_mirrors: [], skip_tls_verify_registry: [],
19
+ trusted_ca_cert_source_files: [], trusted_ca_cert_target_file: '/kaniko/ssl/certs/ca-certificates.crt'
21
20
  }.merge(kaniko_opts)
22
21
  super name, opts
23
22
  update
@@ -26,11 +25,19 @@ module Pipedawg
26
25
  def update # rubocop:disable Metrics/AbcSize
27
26
  require 'json'
28
27
  opts[:image] = kaniko_opts[:image] if kaniko_opts[:image]
29
- opts[:script] = config + cert_copies + file_copies + Array(kaniko_cmd)
28
+ opts[:script] = debug + config + cert_copies + file_copies + Array(kaniko_cmd)
30
29
  end
31
30
 
32
31
  private
33
32
 
33
+ def debug
34
+ if kaniko_opts[:debug]
35
+ Pipedawg::Util.echo_proxy_vars
36
+ else
37
+ []
38
+ end
39
+ end
40
+
34
41
  def config
35
42
  ["echo #{kaniko_opts[:config].to_json.inspect} > \"#{kaniko_opts[:config_file]}\""]
36
43
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # qualys_scan_job class
5
+ class QualysScanJob < Job # rubocop:disable Metrics/ClassLength
6
+ attr_accessor :qualys_opts
7
+
8
+ def initialize(name = 'build', opts = {}, qualys_opts = {})
9
+ @qualys_opts = {
10
+ acceptable_risk: '${QUALYS_ACCEPTABLE_IMAGE_RISK}',
11
+ artifacts: { expire_in: '1 month', paths: ['software.json', 'vulnerabilities.json'], when: 'always' },
12
+ config: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } },
13
+ debug: true, gateway: '${QUALYS_GATEWAY}', image: nil, password: '${QUALYS_PASSWORD}', rules: nil,
14
+ scan_image: '${QUALYS_IMAGE}', scan_target_prefix: 'qualys_scan_target', tags: nil, user: '${QUALYS_USERNAME}',
15
+ variables: { GIT_STRATEGY: 'clone' }
16
+ }.merge(qualys_opts)
17
+ super name, opts
18
+ update
19
+ end
20
+
21
+ def update # rubocop:disable Metrics/AbcSize
22
+ require 'json'
23
+ opts[:artifacts] = qualys_opts[:artifacts] if qualys_opts[:artifacts]
24
+ opts[:image] = qualys_opts[:image]
25
+ opts[:rules] = qualys_opts[:rules] if qualys_opts[:rules]
26
+ opts[:tags] = qualys_opts[:tags] if qualys_opts[:tags]
27
+ opts[:variables] = qualys_opts[:variables] if qualys_opts[:variables]
28
+ opts[:script] =
29
+ debug + config + image + clean_config + token + scan_start + scan_complete + artifacts + severities + outputs
30
+ end
31
+
32
+ private
33
+
34
+ def debug # rubocop:disable Metrics/MethodLength
35
+ if qualys_opts[:debug]
36
+ Pipedawg::Util.echo_proxy_vars + [
37
+ 'echo Qualys settings:', "echo Qualys gateway: \"#{qualys_opts[:gateway]}\"",
38
+ "echo Qualys username: \"#{qualys_opts[:user]}\"",
39
+ "if [ \"#{qualys_opts[:password]}\" != '' ]; then " \
40
+ 'echo Qualys password is not empty; else ' \
41
+ 'echo Qualys password is not set; exit 1; fi'
42
+ ]
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ def config
49
+ ['export CONFIG=$(mktemp -d)', "echo #{qualys_opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
50
+ end
51
+
52
+ def image
53
+ [
54
+ "image_target=\"#{qualys_opts[:scan_target_prefix]}:$(echo #{qualys_opts[:scan_image]} | sed 's/^[^/]*\\///'| sed 's/[:/]/-/g')\"", # rubocop:disable Layout/LineLength
55
+ "docker --config=\"${CONFIG}\" pull \"#{qualys_opts[:scan_image]}\"",
56
+ "docker image tag \"#{qualys_opts[:scan_image]}\" \"${image_target}\"",
57
+ "image_id=$(docker inspect --format=\"{{index .Id}}\" \"#{qualys_opts[:scan_image]}\" | cut -c8-19)",
58
+ 'echo "Image ID: ${image_id}"'
59
+ ]
60
+ end
61
+
62
+ def clean_config
63
+ [
64
+ 'rm -f "${CONFIG}/config.json"',
65
+ 'rmdir "${CONFIG}"'
66
+ ]
67
+ end
68
+
69
+ def token
70
+ ["token=$(curl -s --location --request POST \"https://#{qualys_opts[:gateway]}/auth\" --header \"Content-Type: application/x-www-form-urlencoded\" --data-urlencode \"username=#{qualys_opts[:user]}\" --data-urlencode \"password=#{qualys_opts[:password]}\" --data-urlencode \"token=true\")"] # rubocop:disable Layout/LineLength
71
+ end
72
+
73
+ def scan_start
74
+ [
75
+ 'while true; do ' \
76
+ "result=$(curl -s -o /dev/null -w ''%{http_code}'' --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\"); " + # rubocop:disable Layout/LineLength, Style/FormatStringToken
77
+ 'echo "Waiting for scan to start..."; ' \
78
+ 'echo " Result: ${result}"; ' \
79
+ 'if [ "${result}" = "200" ]; then break; fi; ' \
80
+ 'sleep 10; done'
81
+ ]
82
+ end
83
+
84
+ def scan_complete
85
+ [
86
+ 'while true; do ' \
87
+ "result=$(curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id\" --header \"Authorization: Bearer $token\" | jq -r '.scanStatus'); " + # rubocop:disable Layout/LineLength
88
+ 'echo "Waiting for scan to complete..."; ' \
89
+ 'echo " Result: ${result}"; ' \
90
+ 'if [ "${result}" = "SUCCESS" ]; then break; fi; ' \
91
+ 'sleep 10; done; sleep 30'
92
+ ]
93
+ end
94
+
95
+ def artifacts
96
+ [
97
+ "curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/software\" --header \"Authorization: Bearer $token\" | jq . > software.json", # rubocop:disable Layout/LineLength
98
+ "curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/vuln\" --header \"Authorization: Bearer $token\" | jq . > vulnerabilities.json" # rubocop:disable Layout/LineLength
99
+ ]
100
+ end
101
+
102
+ def severities
103
+ [
104
+ "response=$(curl -s --location --request GET \"https://#{qualys_opts[:gateway]}/csapi/v1.2/images/$image_id/vuln/count\" --header \"Authorization: Bearer $token\")", # rubocop:disable Layout/LineLength
105
+ 'severity5=$(jq -r ".severity5Count" <<< "${response}")',
106
+ 'severity4=$(jq -r ".severity4Count" <<< "${response}")'
107
+ ]
108
+ end
109
+
110
+ def outputs # rubocop:disable Metrics/MethodLength
111
+ [
112
+ 'if [ "$severity5" = "null" ]; then ' \
113
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
114
+ 'exit 1; fi',
115
+ 'if [ "$severity4" = "null" ]; then ' \
116
+ 'echo "ERROR: Wrong ImageID or problem during vulnerabilities count." >&2; ' \
117
+ 'exit 1; fi',
118
+ 'echo "Severity5: $severity5, Severity4: $severity4"',
119
+ 'risk=$((($severity5*3)+($severity4)))',
120
+ 'echo "Risk: $risk"',
121
+ "if (($risk > \"#{qualys_opts[:acceptable_risk]}\")); then " \
122
+ 'echo "Too many vulnerabilities. Severity5: $severity5, Severity4: $severity4" >&2; ' \
123
+ 'exit 1; fi'
124
+ ]
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # skopeo_copy_job class
5
+ class SkopeoCopyJob < Job
6
+ attr_accessor :skopeo_opts
7
+
8
+ def initialize(name = 'build', opts = {}, skopeo_opts = {})
9
+ @skopeo_opts = {
10
+ config: { '$CI_REGISTRY': { username: '$CI_REGISTRY_USER', password: '$CI_REGISTRY_PASSWORD' } },
11
+ copy_image: nil, debug: true, destinations: [{ copy_image: nil, flags: [], options: {} }],
12
+ flags: [], logins: {}, options: {}, skopeo: 'skopeo', stage: '${CI_PROJECT_DIR}/stage',
13
+ image: { entrypoint: [''], name: 'quay.io/skopeo/stable:latest' },
14
+ trusted_ca_cert_source_files: [], trusted_ca_cert_target_file: '/etc/docker/certs.d/ca.crt'
15
+ }.merge(skopeo_opts)
16
+ super name, opts
17
+ update
18
+ end
19
+
20
+ def update # rubocop:disable Metrics/AbcSize
21
+ require 'json'
22
+ opts[:image] = skopeo_opts[:image] if skopeo_opts[:image]
23
+ opts[:rules] = skopeo_opts[:rules] if skopeo_opts[:rules]
24
+ opts[:script] = debug + config + cert_copies + login + mkstage + pull + (
25
+ skopeo_opts[:destinations].map { |d| push(d) }
26
+ ).flatten(1)
27
+ end
28
+
29
+ private
30
+
31
+ def debug
32
+ if skopeo_opts[:debug]
33
+ Pipedawg::Util.echo_proxy_vars
34
+ else
35
+ []
36
+ end
37
+ end
38
+
39
+ def config
40
+ ['export CONFIG=$(mktemp -d)', "echo #{skopeo_opts[:config].to_json.inspect} > \"${CONFIG}/config.json\""]
41
+ end
42
+
43
+ def cert_copies
44
+ ["mkdir -p $(dirname \"#{skopeo_opts[:trusted_ca_cert_target_file]}\")"] +
45
+ Array(skopeo_opts[:trusted_ca_cert_source_files]).map do |cert|
46
+ "cat \"#{cert}\" >> \"#{skopeo_opts[:trusted_ca_cert_target_file]}\""
47
+ end
48
+ end
49
+
50
+ def login
51
+ skopeo_opts.fetch(:logins, {}).map do |k, v|
52
+ "echo \"#{v['password']}\" | #{skopeo_opts[:skopeo]} login --authfile \"${CONFIG}/config.json\" --username \"#{v['username']}\" --password-stdin \"#{k}\"" # rubocop:disable Layout/LineLength
53
+ end
54
+ end
55
+
56
+ def mkstage
57
+ ["mkdir -p \"#{skopeo_opts[:stage]}\""]
58
+ end
59
+
60
+ def pull
61
+ copy(skopeo_opts, "docker://#{skopeo_opts[:copy_image]}", "\"dir://#{skopeo_opts[:stage]}\"")
62
+ end
63
+
64
+ def push(destination_opts)
65
+ copy(destination_opts, "\"dir://#{skopeo_opts[:stage]}\"", "docker://#{destination_opts[:copy_image]}")
66
+ end
67
+
68
+ def copy(opts, source, destination)
69
+ Array(["#{skopeo_opts[:skopeo]} copy --authfile \"${CONFIG}/config.json\"", flags(opts), options(opts), source,
70
+ destination].reject(&:empty?).join(' '))
71
+ end
72
+
73
+ def flags(opts)
74
+ opts.fetch(:flags, []).uniq.map { |f| "--#{f}" }.join(' ')
75
+ end
76
+
77
+ def options(opts)
78
+ opts.fetch(:options, {}).map { |k, v| "--#{k} \"#{v}\"" }.join(' ')
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pipedawg
4
+ # util class
5
+ class Util
6
+ def self.expand_env_vars(item) # rubocop:disable Metrics/MethodLength
7
+ case item
8
+ when Array
9
+ item.map { |i| expand_env_vars(i) }
10
+ when Hash
11
+ item.each { |k, v| item[k] = expand_env_vars(v) }
12
+ item.transform_keys! { |k| expand_env_vars(k) }
13
+ item
14
+ when String
15
+ item.gsub(/\${([^} ]+)}/) do |e|
16
+ ENV[e.gsub('${', '').gsub('}', '')]
17
+ end
18
+ else
19
+ item
20
+ end
21
+ end
22
+
23
+ def self.puts_proxy_vars
24
+ puts 'Proxy settings:'
25
+ puts "http_proxy: #{ENV['http_proxy']}"
26
+ puts "https_proxy: #{ENV['https_proxy']}"
27
+ puts "no_proxy: #{ENV['no_proxy']}"
28
+ puts "HTTP_PROXY: #{ENV['HTTP_PROXY']}"
29
+ puts "HTTPS_PROXY: #{ENV['HTTPS_PROXY']}"
30
+ puts "NO_PROXY: #{ENV['NO_PROXY']}"
31
+ end
32
+
33
+ def self.echo_proxy_vars
34
+ script = ['echo Proxy settings:']
35
+ script << 'echo http_proxy: "${http_proxy}"'
36
+ script << 'echo https_proxy: "${https_proxy}"'
37
+ script << 'echo no_proxy: "${no_proxy}"'
38
+ script << 'echo HTTP_PROXY: "${HTTP_PROXY}"'
39
+ script << 'echo HTTPS_PROXY: "${HTTPS_PROXY}"'
40
+ script << 'echo NO_PROXY: "${NO_PROXY}"'
41
+ script
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pipedawg
4
- VERSION = '0.3.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/pipedawg.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  require 'pipedawg/job'
4
4
  require 'pipedawg/helm_copy_job'
5
- require 'pipedawg/kaniko_job'
5
+ require 'pipedawg/kaniko_build_job'
6
6
  require 'pipedawg/pipeline'
7
+ require 'pipedawg/qualys_scan_job'
8
+ require 'pipedawg/skopeo_copy_job'
9
+ require 'pipedawg/util'
7
10
  require 'pipedawg/version'
8
11
 
9
12
  module Pipedawg
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipedawg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - harbottle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-08 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Generate GitLab CI pipelines.
14
14
  email:
@@ -22,8 +22,11 @@ files:
22
22
  - lib/pipedawg.rb
23
23
  - lib/pipedawg/helm_copy_job.rb
24
24
  - lib/pipedawg/job.rb
25
- - lib/pipedawg/kaniko_job.rb
25
+ - lib/pipedawg/kaniko_build_job.rb
26
26
  - lib/pipedawg/pipeline.rb
27
+ - lib/pipedawg/qualys_scan_job.rb
28
+ - lib/pipedawg/skopeo_copy_job.rb
29
+ - lib/pipedawg/util.rb
27
30
  - lib/pipedawg/version.rb
28
31
  homepage: https://github.com/liger1978/pipedawg
29
32
  licenses: