puppetserver-ca 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ module Puppetserver
2
+ module Ca
3
+ module Utils
4
+ def self.parse_without_raising(parser, args)
5
+ all, not_flags, malformed_flags, unknown_flags = [], [], [], []
6
+
7
+ begin
8
+ # OptionParser calls this block when it finds a value that doesn't
9
+ # start with one or two dashes and doesn't follow a flag that
10
+ # consumes a value.
11
+ parser.order!(args) do |not_flag|
12
+ not_flags << not_flag
13
+ all << not_flag
14
+ end
15
+ rescue OptionParser::MissingArgument => e
16
+ malformed_flags += e.args
17
+ all += e.args
18
+
19
+ retry
20
+ rescue OptionParser::ParseError => e
21
+ flag = e.args.first
22
+ unknown_flags << flag
23
+ all << flag
24
+
25
+ if does_not_contain_argument(flag) &&
26
+ args.first &&
27
+ next_arg_is_not_another_flag(args.first)
28
+
29
+ value = args.shift
30
+ unknown_flags << value
31
+ all << value
32
+ end
33
+
34
+ retry
35
+ end
36
+
37
+ return all, not_flags, malformed_flags, unknown_flags
38
+ end
39
+
40
+ def self.parse_with_errors(parser, args)
41
+ errors = []
42
+
43
+ _, non_flags, malformed_flags, unknown_flags = parse_without_raising(parser, args)
44
+
45
+ malformed_flags.each {|f| errors << " Missing argument to flag `#{f}`" }
46
+ unknown_flags.each {|f| errors << " Unknown flag or argument `#{f}`" }
47
+ non_flags.each {|f| errors << " Unknown input `#{f}`" }
48
+
49
+ errors
50
+ end
51
+
52
+ def self.handle_errors(log, errors, usage = nil)
53
+ unless errors.empty?
54
+ log.err 'Error:'
55
+ errors.each {|e| log.err e }
56
+
57
+ if usage
58
+ log.err ''
59
+ log.err usage
60
+ end
61
+
62
+ return true
63
+ else
64
+ return false
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # eg. --flag=argument-to-flag
71
+ def self.does_not_contain_argument(flag)
72
+ !flag.include?('=')
73
+ end
74
+
75
+ def self.next_arg_is_not_another_flag(maybe_an_arg)
76
+ !maybe_an_arg.start_with?('-')
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,5 +1,5 @@
1
1
  module Puppetserver
2
2
  module Ca
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -0,0 +1,48 @@
1
+ # A setting that represents a span of time to live, and evaluates to Numeric
2
+ # seconds to live where 0 means shortest possible time to live, a positive numeric value means time
3
+ # to live in seconds, and the symbolic entry 'unlimited' is an infinite amount of time.
4
+ #
5
+ module Puppetserver
6
+ module Settings
7
+ class TTLSetting
8
+ # How we convert from various units to seconds.
9
+ UNITMAP = {
10
+ # 365 days isn't technically a year, but is sufficient for most purposes
11
+ "y" => 365 * 24 * 60 * 60,
12
+ "d" => 24 * 60 * 60,
13
+ "h" => 60 * 60,
14
+ "m" => 60,
15
+ "s" => 1
16
+ }
17
+
18
+ # A regex describing valid formats with groups for capturing the value and units
19
+ FORMAT = /^(\d+)(y|d|h|m|s)?$/
20
+
21
+ attr_reader :errors, :munged_value
22
+
23
+ def initialize(name, setting_value)
24
+ @errors = []
25
+ @munged_value = munge(setting_value, name)
26
+ end
27
+
28
+ # Convert the value to Numeric, parsing numeric string with units if necessary.
29
+ def munge(value, name)
30
+ case
31
+ when value.is_a?(Numeric)
32
+ if value < 0
33
+ @errors << "Invalid negative 'time to live' #{value.inspect} - did you mean 'unlimited'?"
34
+ end
35
+ value
36
+
37
+ when value == 'unlimited'
38
+ Float::INFINITY
39
+
40
+ when (value.is_a?(String) and value =~ FORMAT)
41
+ $1.to_i * UNITMAP[$2 || 's']
42
+ else
43
+ @errors << "Invalid 'time to live' format '#{value.inspect}' for parameter: #{name}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,78 @@
1
+ require 'fileutils'
2
+ require 'etc'
3
+
4
+ module Puppetserver
5
+ module Utils
6
+ class FileUtilities
7
+
8
+ def self.instance
9
+ @instance ||= new
10
+ end
11
+
12
+ def self.write_file(*args)
13
+ instance.write_file(*args)
14
+ end
15
+
16
+ def self.ensure_dir(setting)
17
+ instance.ensure_dir(setting)
18
+ end
19
+
20
+ def self.ensure_file(location, content, mode)
21
+ if !File.exist?(location)
22
+ instance.write_file(location, content, mode)
23
+ end
24
+ end
25
+
26
+ def self.validate_file_paths(one_or_more_paths)
27
+ errors = []
28
+ Array(one_or_more_paths).each do |path|
29
+ if !File.exist?(path) || !File.readable?(path)
30
+ errors << "Could not read file '#{path}'"
31
+ end
32
+ end
33
+
34
+ errors
35
+ end
36
+
37
+ def initialize
38
+ @user, @group = find_user_and_group
39
+ end
40
+
41
+ def find_user_and_group
42
+ if !running_as_root?
43
+ return Process.euid, Process.egid
44
+ else
45
+ if pe_puppet_exists?
46
+ return 'pe-puppet', 'pe-puppet'
47
+ else
48
+ return 'puppet', 'puppet'
49
+ end
50
+ end
51
+ end
52
+
53
+ def running_as_root?
54
+ !Gem.win_platform? && Process.euid == 0
55
+ end
56
+
57
+ def pe_puppet_exists?
58
+ !!(Etc.getpwnam('pe-puppet') rescue nil)
59
+ end
60
+
61
+ def write_file(path, one_or_more_objects, mode)
62
+ File.open(path, 'w', mode) do |f|
63
+ Array(one_or_more_objects).each do |object|
64
+ f.puts object.to_s
65
+ end
66
+ end
67
+ FileUtils.chown(@user, @group, path)
68
+ end
69
+
70
+ def ensure_dir(setting)
71
+ if !File.exist?(setting)
72
+ FileUtils.mkdir_p(setting, mode: 0750)
73
+ FileUtils.chown(@user, @group, setting)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,116 @@
1
+ require 'openssl'
2
+ require 'net/https'
3
+
4
+ module Puppetserver
5
+ module Utils
6
+ # Utilities for doing HTTPS against the CA that wraps Net::HTTP constructs
7
+ class HttpClient
8
+
9
+ HEADERS = {
10
+ 'User-Agent' => 'PuppetserverCaCli',
11
+ 'Content-Type' => 'application/json',
12
+ 'Accept' => 'application/json'
13
+ }
14
+
15
+ attr_reader :store
16
+
17
+ def initialize(localcacert, crl_usage, hostcrl)
18
+ @store = make_store(localcacert, crl_usage, hostcrl)
19
+ end
20
+
21
+ # Returns a URI-like wrapper around CA specific urls
22
+ def make_ca_url(host, port, resource_type = nil, certname = nil)
23
+ URL.new('https', host, port, 'puppet-ca', 'v1', resource_type, certname)
24
+ end
25
+
26
+ # Takes an instance URL (defined lower in the file), and creates a
27
+ # connection. The given block is passed our own Connection object.
28
+ # The Connection object should have HTTP verbs defined on it that take
29
+ # a body (and optional overrides). Returns whatever the block given returned.
30
+ def with_connection(url, &block)
31
+ request = ->(conn) { block.call(Connection.new(conn, url)) }
32
+
33
+ Net::HTTP.start(url.host, url.port,
34
+ use_ssl: true, cert_store: @store,
35
+ &request)
36
+ end
37
+
38
+ private
39
+ # Helper class that wraps a Net::HTTP connection, a HttpClient::URL
40
+ # and defines methods named after HTTP verbs that are called on the
41
+ # saved connection, returning a Result.
42
+ class Connection
43
+ def initialize(net_http_connection, url_struct)
44
+ @conn = net_http_connection
45
+ @url = url_struct
46
+ end
47
+
48
+ def get(url_overide = nil)
49
+ url = url_overide || @url
50
+
51
+ request = Net::HTTP::Get.new(url.to_uri, HEADERS)
52
+ result = @conn.request(request)
53
+
54
+ Result.new(result.code, result.body)
55
+ end
56
+
57
+ def put(body, url_override = nil)
58
+ url = url_override || @url
59
+
60
+ request = Net::HTTP::Put.new(url.to_uri, HEADERS)
61
+ request.body = body
62
+ result = @conn.request(request)
63
+
64
+ Result.new(result.code, result.body)
65
+ end
66
+
67
+ def delete(url_override = nil)
68
+ url = url_override || @url
69
+
70
+ result = @conn.request(Net::HTTP::Delete.new(url.to_uri, HEADERS))
71
+
72
+ Result.new(result.code, result.body)
73
+ end
74
+ end
75
+
76
+ # Just provide the bits of Net::HTTPResponse we care about
77
+ Result = Struct.new(:code, :body)
78
+
79
+ # Like URI, but not... maybe of suspicious value
80
+ URL = Struct.new(:protocol, :host, :port,
81
+ :endpoint, :version,
82
+ :resource_type, :resource_name) do
83
+ def full_url
84
+ protocol + '://' + host + ':' + port + '/' +
85
+ [endpoint, version, resource_type, resource_name].join('/')
86
+ end
87
+
88
+ def to_uri
89
+ URI(full_url)
90
+ end
91
+ end
92
+
93
+ def make_store(bundle, crl_usage, crls = nil)
94
+ store = OpenSSL::X509::Store.new
95
+ store.purpose = OpenSSL::X509::PURPOSE_ANY
96
+ store.add_file(bundle)
97
+
98
+ if crl_usage != :ignore
99
+
100
+ flags = OpenSSL::X509::V_FLAG_CRL_CHECK
101
+ if crl_usage == :chain
102
+ flags |= OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
103
+ end
104
+
105
+ store.flags = flags
106
+ delimiter = /-----BEGIN X509 CRL-----.*?-----END X509 CRL-----/m
107
+ File.read(crls).scan(delimiter).each do |crl|
108
+ store.add_crl(OpenSSL::X509::CRL.new(crl))
109
+ end
110
+ end
111
+
112
+ store
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,25 @@
1
+ module Puppetserver
2
+ module Utils
3
+ class SigningDigest
4
+
5
+ attr_reader :errors, :digest
6
+
7
+ def initialize
8
+ @errors = []
9
+ if OpenSSL::Digest.const_defined?('SHA256')
10
+ @digest = OpenSSL::Digest::SHA256.new
11
+ elsif OpenSSL::Digest.const_defined?('SHA1')
12
+ @digest = OpenSSL::Digest::SHA1.new
13
+ elsif OpenSSL::Digest.const_defined?('SHA512')
14
+ @digest = OpenSSL::Digest::SHA512.new
15
+ elsif OpenSSL::Digest.const_defined?('SHA384')
16
+ @digest = OpenSSL::Digest::SHA384.new
17
+ elsif OpenSSL::Digest.const_defined?('SHA224')
18
+ @digest = OpenSSL::Digest::SHA224.new
19
+ else
20
+ @errors << "Error: No FIPS 140-2 compliant digest algorithm in OpenSSL::Digest"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_runtime_dependency "facter", [">= 2.0.1", "< 4"]
24
+
23
25
  spec.add_development_dependency "bundler", "~> 1.16"
24
26
  spec.add_development_dependency "rake", "~> 10.0"
25
27
  spec.add_development_dependency "rspec", "~> 3.0"
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppetserver-ca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-27 00:00:00.000000000 Z
11
+ date: 2018-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: facter
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 2.0.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '4'
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: bundler
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -73,15 +93,27 @@ files:
73
93
  - bin/setup
74
94
  - exe/puppetserver-ca
75
95
  - lib/puppetserver/ca.rb
96
+ - lib/puppetserver/ca/clean_action.rb
76
97
  - lib/puppetserver/ca/cli.rb
77
98
  - lib/puppetserver/ca/config_utils.rb
99
+ - lib/puppetserver/ca/create_action.rb
100
+ - lib/puppetserver/ca/generate_action.rb
101
+ - lib/puppetserver/ca/host.rb
78
102
  - lib/puppetserver/ca/import_action.rb
103
+ - lib/puppetserver/ca/list_action.rb
79
104
  - lib/puppetserver/ca/logger.rb
80
105
  - lib/puppetserver/ca/puppet_config.rb
81
106
  - lib/puppetserver/ca/puppetserver_config.rb
107
+ - lib/puppetserver/ca/revoke_action.rb
108
+ - lib/puppetserver/ca/sign_action.rb
82
109
  - lib/puppetserver/ca/stub.rb
110
+ - lib/puppetserver/ca/utils.rb
83
111
  - lib/puppetserver/ca/version.rb
84
112
  - lib/puppetserver/ca/x509_loader.rb
113
+ - lib/puppetserver/settings/ttl_setting.rb
114
+ - lib/puppetserver/utils/file_utilities.rb
115
+ - lib/puppetserver/utils/http_client.rb
116
+ - lib/puppetserver/utils/signing_digest.rb
85
117
  - puppetserver-ca.gemspec
86
118
  homepage: https://github.com/puppetlabs/puppetserver-ca-cli/
87
119
  licenses: