diff --git a/bundler.d/puppetca_token_whitelisting.rb b/bundler.d/puppetca_token_whitelisting.rb new file mode 100644 index 0000000000..a6eede1766 --- /dev/null +++ b/bundler.d/puppetca_token_whitelisting.rb @@ -0,0 +1,7 @@ +group :puppetca_token_whitelisting do + if RUBY_VERSION < '2.1' + gem 'jwt', '1.5.6' + else + gem 'jwt' + end +end diff --git a/config/settings.d/puppetca.yml.example b/config/settings.d/puppetca.yml.example index 965cf06554..0d2ebf348e 100644 --- a/config/settings.d/puppetca.yml.example +++ b/config/settings.d/puppetca.yml.example @@ -4,6 +4,7 @@ # valid providers: # - puppetca_hostname_whitelisting (verify CSRs based on a hostname whitelist) +# - puppetca_token_whitelisting (verify CSRs based on a token whitelist) #:use_provider: puppetca_hostname_whitelisting #:ssldir: /var/lib/puppet/ssl #:puppetca_use_sudo: true diff --git a/config/settings.d/puppetca_token_whitelisting.yml.example b/config/settings.d/puppetca_token_whitelisting.yml.example new file mode 100644 index 0000000000..dae616bbd1 --- /dev/null +++ b/config/settings.d/puppetca_token_whitelisting.yml.example @@ -0,0 +1,10 @@ +--- +# +# Configuration of the PuppetCA token_whitelisting provider +# + +#:sign_all: false +#:token_ttl: 360 +#:tokens_file: /var/lib/foreman-proxy/tokens.yml +# Which certificate to use when encrypting tokens (nil to use the SSL certificate) +#:certificate: nil diff --git a/lib/smart_proxy_main.rb b/lib/smart_proxy_main.rb index 61a7985213..65fe1b2b0f 100644 --- a/lib/smart_proxy_main.rb +++ b/lib/smart_proxy_main.rb @@ -68,6 +68,7 @@ module Proxy require 'dhcp_libvirt/dhcp_libvirt' require 'puppetca/puppetca' require 'puppetca_hostname_whitelisting/puppetca_hostname_whitelisting' + require 'puppetca_token_whitelisting/puppetca_token_whitelisting' require 'puppet_proxy/puppet' require 'puppet_proxy_customrun/puppet_proxy_customrun' require 'puppet_proxy_legacy/puppet_proxy_legacy' diff --git a/modules/puppetca/puppetca_api.rb b/modules/puppetca/puppetca_api.rb index a7e1cf0c1e..5499eabb12 100644 --- a/modules/puppetca/puppetca_api.rb +++ b/modules/puppetca/puppetca_api.rb @@ -26,16 +26,30 @@ class Api < ::Sinatra::Base end end - post "/autosign/:certname" do + post /\/autosign\/([^\/\s]+)(\/([\d]*))?/ do content_type :json - certname = params[:certname] + certname = params['captures'][0] + ttl = params['captures'][2] begin - autosigner.autosign(certname) + autosigner.autosign(certname, ttl) rescue => e log_halt 406, "Failed to enable autosign for #{certname}: #{e}" end end + post "/validate" do + content_type :json + unless autosigner.respond_to?('validate_csr') + log_halt 501, "Provider only supports trivial autosigning" + end + begin + request.body.rewind + autosigner.validate_csr(request.body.read) ? 200 : 404 + rescue => e + log_halt 406, "Failed to validate CSR: #{e}" + end + end + delete "/autosign/:certname" do content_type :json certname = params[:certname] diff --git a/modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner.rb b/modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner.rb index 6b0c272790..98557e7a9c 100644 --- a/modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner.rb +++ b/modules/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner.rb @@ -33,7 +33,7 @@ def disable certname # add certname to puppet autosign file # parameter is certname to use - def autosign certname + def autosign certname, ttl FileUtils.touch(autosign_file) unless File.exist?(autosign_file) open(autosign_file, File::RDWR) do |autosign| diff --git a/modules/puppetca_token_whitelisting/plugin_configuration.rb b/modules/puppetca_token_whitelisting/plugin_configuration.rb new file mode 100644 index 0000000000..d108d9ad2e --- /dev/null +++ b/modules/puppetca_token_whitelisting/plugin_configuration.rb @@ -0,0 +1,14 @@ +module ::Proxy::PuppetCa::TokenWhitelisting + class PluginConfiguration + def load_classes + require 'puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner' + require 'puppetca_token_whitelisting/puppetca_token_whitelisting_csr' + require 'puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage' + end + + def load_dependency_injection_wirings(container_instance, settings) + container_instance.dependency :autosigner, lambda { ::Proxy::PuppetCa::TokenWhitelisting::Autosigner.new } + end + end +end + diff --git a/modules/puppetca_token_whitelisting/puppetca_token_whitelisting.rb b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting.rb new file mode 100644 index 0000000000..aef5ee99cb --- /dev/null +++ b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting.rb @@ -0,0 +1,2 @@ +require 'puppetca_token_whitelisting/plugin_configuration' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_plugin' diff --git a/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner.rb b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner.rb new file mode 100644 index 0000000000..ea44461ac4 --- /dev/null +++ b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner.rb @@ -0,0 +1,123 @@ +require 'jwt' +require 'openssl' + +module ::Proxy::PuppetCa::TokenWhitelisting + class Autosigner + include ::Proxy::Log + include ::Proxy::Util + + JWT_ALGORITHM = 'RS512' + + def tokens_file + Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.tokens_file + end + + def sign_all + Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.sign_all + end + + def smartproxy_cert + @certificate ||= OpenSSL::PKey::RSA.new File.read cert_file + end + + def storage + Proxy::PuppetCa::TokenWhitelisting::TokenStorage.new tokens_file + end + + def token_ttl + Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.token_ttl + end + + def cert_file + return Proxy::SETTINGS.ssl_private_key.to_s if Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.certificate.nil? + + file = Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.certificate + unless File.exist?(file) + File.write file, OpenSSL::PKey::RSA.generate(2048) + end + file + end + + # Invalidate a token based on the certname + def disable certname + storage.lock do + tokens = storage.read + tokens.delete_if do |token| + begin + decoded = JWT.decode(token, smartproxy_cert.public_key, true, algorithm: JWT_ALGORITHM) + decoded.first['certname'] == certname + rescue JWT::ExpiredSignature + true + end + end + storage.locked_write tokens + end + end + + # Create a new token for a certname + def autosign certname, ttl + ttl = ttl.to_i > 0 ? ttl.to_i : token_ttl + payload = { certname: certname, exp: Time.now.to_i + ttl * 60 } + token = JWT.encode payload, smartproxy_cert, JWT_ALGORITHM + storage.add token + { generated_token: token }.to_json + end + + # List the hosts that are currently valid + def autosign_list + storage.read.collect do |token| + begin + decoded = JWT.decode(token, smartproxy_cert.public_key, true, algorithm: JWT_ALGORITHM) + decoded.first['certname'] + rescue JWT::ExpiredSignature + nil + end + end.reject { |certname| certname.nil? } + end + + # Check whether a csr is valid and should be signed + # by checking its token if it exists + def validate_csr csr + if csr.nil? + logger.warn "Request did not include a CSR." + return false + end + if sign_all + logger.warn "Signing CSR without token verification." + return true + end + begin + req = Proxy::PuppetCa::TokenWhitelisting::CSR.new csr + token = req.challenge_password + rescue + logger.warn "Invalid CSR" + return false + end + if token.nil? + logger.warn "CSR did not include a token." + return false + end + validate_token token + end + + def validate_token token + # token didnt expire? + begin + JWT.decode(token, smartproxy_cert.public_key, true, algorithm: JWT_ALGORITHM) + rescue JWT::ExpiredSignature + logger.warn "Token already expired." + return false + rescue JWT::DecodeError + logger.warn "Failed to decode token." + return false + end + # token in our list? + unless storage.read.include? token + logger.warn "Certname not valid." + return false + end + storage.remove token + return true + end + end +end diff --git a/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_csr.rb b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_csr.rb new file mode 100644 index 0000000000..3da76d7cd4 --- /dev/null +++ b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_csr.rb @@ -0,0 +1,25 @@ +module Proxy::PuppetCa::TokenWhitelisting + class CSR + attr_reader :csr + + def initialize(raw_csr) + @csr = OpenSSL::X509::Request.new(raw_csr) + end + + def challenge_password + attribute = custom_attributes.detect do |attr| + ['challengePassword', '1.2.840.113549.1.9.7'].include?(attr[:oid]) + end + attribute ? attribute[:value] : nil + end + + def custom_attributes + @csr.attributes.map do |attr| + { + oid: attr.oid, + value: attr.value.value.first.value + } + end + end + end +end diff --git a/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_plugin.rb b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_plugin.rb new file mode 100644 index 0000000000..19c96374c2 --- /dev/null +++ b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_plugin.rb @@ -0,0 +1,11 @@ +module ::Proxy::PuppetCa::TokenWhitelisting + class Plugin < ::Proxy::Provider + plugin :puppetca_token_whitelisting, ::Proxy::VERSION + + requires :puppetca, ::Proxy::VERSION + default_settings :sign_all => false, :tokens_file => '/var/lib/foreman-proxy/tokens.yml', :token_ttl => 360, :certificate => nil + + load_classes ::Proxy::PuppetCa::TokenWhitelisting::PluginConfiguration + load_dependency_injection_wirings ::Proxy::PuppetCa::TokenWhitelisting::PluginConfiguration + end +end diff --git a/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage.rb b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage.rb new file mode 100644 index 0000000000..9f66bb12e9 --- /dev/null +++ b/modules/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage.rb @@ -0,0 +1,52 @@ +module ::Proxy::PuppetCa::TokenWhitelisting + class TokenStorage + include ::Proxy::Log + include ::Proxy::Util + + def initialize tokens_file + @tokens_file = tokens_file + ensure_file + end + + def ensure_file + unless File.exist?(@tokens_file) + FileUtils.mkdir_p File.dirname(@tokens_file) + FileUtils.touch @tokens_file + write [] + end + end + + def read + YAML.safe_load File.read @tokens_file + end + + def write content + lock do + locked_write content + end + end + + def locked_write content + File.write @tokens_file, content.to_yaml + end + + def lock &block + File.open(@tokens_file, "r+") do |f| + begin + f.flock File::LOCK_EX + yield + ensure + f.flock File::LOCK_UN + end + end + end + + def add entry + write read.push entry + end + + def remove entry + write read.delete_if { |data| data == entry } + end + end +end diff --git a/test/fixtures/puppetca/csr_example.pem b/test/fixtures/puppetca/csr_example.pem new file mode 100644 index 0000000000..89eeeabfb5 --- /dev/null +++ b/test/fixtures/puppetca/csr_example.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEdTCCAl0CAQAwGzEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNvbTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAK+eSI5LYrBUROs02A9DYPqEpnN1YokD +MSb47NQW0A4+o7B7h/B3HFN2Moo/US/zNLDvHGuVDZIaIpudtNBTBSX0MQPtF8tQ +Lm7WA6wGwqpX3eR3OLXdWR/T4wIvFQ1gvCS/snuW8YennSeAl1Yijtr2EHiPFJ/i +Dy67vGhvXrqVDl/svIf13uw9zcxZ34VfE8zYg5WZ/thlhjbue/KKJdn+riNIYNK4 +gwEjFNER9U38UPZUXMNJCEAXEJ7GKXQUKMXXtybp5jihPxTjbxFKilaIEJNM35ej +9Ra0OREGc0Cc0beaW9+n9ZFBxsfM/NV0nzOos4tmSEyAq54Nbd80ropYivrmSRQf +/yDPijvmC4frL1nhSJaf17rmfw27urbPcObbpuQFXCIlNFJd/CwpPds6ikZ9otDx +yTzPveLbQUCh3CzReODC7jopi7vchPH3cZVvuaE90REKL++3xHqOzRpq5tZ1xc2j +3agiQmTthnzABx6cj/q2ab4YAwpaSIAZFoHtzD8tsyD1WTGkL9jzBkCULd7ZcQdR +AL0PwGLYI2hbCsQV6i9noVZZ19+hHEjjk06lG5SKK+H8eTIybWr9IPU/yZowNPtd +et6samT8ltBLAxqTFffrdsTUTQAoN+ykgsML/N3LUuPACg1G6ge0MQVVvBIfCNiP +SjfPJmthHHc1AgMBAAGgFTATBgkqhkiG9w0BCQcxBhMEMTIzNDANBgkqhkiG9w0B +AQsFAAOCAgEAfTtWZAP8z9pxR6esLkoCfhhXaYzjnzJ5/4r+x/VPpJQEzI37CScG +Dma+UzVkIddCVc5oFtzLtVZtGTaygW2QyR7wu+an0qQBs+MVzkjPPnMLerDUR89c +Nk5DlDMaKFYV3JJpg2G2YdmOR0SEF0640Aw0/Ftx41iTSLSFopDMcTPRbZ9zm2AN +uer2TMIWpio6k6OyEJHkyifQOG1kgI+amVgk21kVRm5qUZV01QduLSxuN8KQKDYT +dmE31BpSQdVYzvrRPV558+NiWSrRheQtLfCl4BUZsZjfgh7OXSiy/yCZ6Co7FeDX +WbVtlIeaFukt5fPD4VKBQVIP296ZiB4BFIDLUcq87DaWbjbO1owS/B89MbAM6Fjy +FHCE3x0nUev3LYrKrJGouHYXiyUNGQk1ilI60y1xc3+B3ErjzpszJB1uBRk1fsem +Np60VUVqNPD7GzxlE/acgEa2Xij1vl+g4yIjWGVv5Fokb4fO6K+n8iSEYKG4+nMr ++vjP7bTcW4GymYPH38TtQfzhXvFMODL8ehiy2xjndEfLMitrbP1vjiLrCZD6gIk3 +alxj7nHJMXF83Rqg/7OhERMVmUGE+tDiRD95bMK/eYq4sj49zke6puf5r0MeW6Fv +jfuZ0r0kJmJF/r2FZEKuScl0uS4/RWUvgUdUFwpZ3i8KzJWJ6NDm7eY= +-----END CERTIFICATE REQUEST----- diff --git a/test/fixtures/puppetca/rsa_cert.pem b/test/fixtures/puppetca/rsa_cert.pem new file mode 100644 index 0000000000..e3adf48580 --- /dev/null +++ b/test/fixtures/puppetca/rsa_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAoPDdXIhv26Ugu04WNoVEUI8FcO/8acp04RkjtjkbUFtYWFlZ +IgP29cA+dcfQEqzDCCbeVyB259yLjNmD48zn1sB139un7sXkay61SP9Q/XhIF2wE +LMSYhpFRE1QXEJo/XZizyf/PXMy+XY8gXBzZb4/X5Qm0KFYc5zmPEW1dfFxswkZA +eOJsX2mD28FKvWDaWLBxTP6lftNMCQogKdQAcV/oNXTr/RqUd8MChW2YDevOzFw3 +s1vVpc4L5gXdU7cblcvUVAqVmnXRL+SHvzY14jOHDtbb1kAbHAD32rrOJWz/tM0i +r1vwo45JN8lFbFHrCKxPO1uvylb0lh8Y9v/VDwIDAQABAoIBAE/jwL5yK7SIX5sa +xAQEW0jx9e1983pAOYyTghqudlSJQ213zLsJ5MlQyLCGo07h215sjOoK/3tRCyS7 +xLCLwnnjx5971fy81wotgWfe9UEdOAwIXnoqjNwfvnMtxtVC/Dll17lp9nFGYSjo +J1QSLg0nBjB3hKqRNH/DHrWz6DgWj0xltKYAim3bbIb+y1Mtb0CmqdfCpawomOtv +R3rlbSTjdy7RfTlAfyzNfG96fLifSJFWz0Kh/xrnlHLvYRh08ZWTS1H7arbSH2AI +8PnbXoFvL6CjT9ZTzOM0t7ltcPEpqaE5vtrB1u6MzGAUqKgLL7Sdq63lF02O7Wzy +VJCoAgECgYEA1cbMy4KdSuqBmC4BvZWdID/dDihPq076n+rJ1gCH5yMOzGaAan6C +0S0Nc95JlWsodxynWKp0Y0Gwd6YQStuppq5G/Ei5Moei2Pe3nHlfSuM1vhZAbE2d +Met7xbJ5bLWpMkf5A84e41iKLH+O3z0z9uMyHYWOdzdVCSzUZthsmsECgYEAwLqH +tSUxQ/e9cJX1CyQbdwDziW5t93U0Al0z9ZkITDeN68nZwBSAt/My40EyNGhPj9X8 +skN46yDsUrofJxJd5SFlzQRvOLAzdbtF/32E4wawmIegSL3VuHCgLgxjq+vjci7T +DL7MKcbAyr9Vbo0jnxkQjll5c7rGyGkCqkoqc88CgYEA1GMRXnNjAGYh4OZ8djVp +iPvKDGHCXUk3OKAUbNfULbAn6K1BkgwkvdsLzX0gECIJbuV54V5gCajewNCsySKZ +1264Obeqv2gAQ51Av2XaZdV8tGD5GmC19z2kl5KsjnOhKMJWxRAIogh6JqGIeYQd +14B7btcc5paDln0CfTcyF0ECgYBO8dk25UY53hf2LybR6ndplrUrXVWkIJkuQrlO +5+GTPspehC8y0/Mp5m+40PLcrsGwXGoHJ0Y3oiPLezvZiFd3zOdjRGa+pMZfeleg +4Ox3Bj1+LUgFo+UQiOnEEwUc3iIrmmyO8vS0RBqish8vipbFy8GRXas3MZHNc95I +nNqo1wKBgGT4lfLGkg2dwInsDnsmq90c1WbZpH5kijnce46VJNxLt6XpnTB/eDoQ +ac9dg5d08ul/PPPEk/aM0oDlwTDSqVBtEjeB4IUlj400W5fFOfKQ9b5PBFTbKVOC +8ca2jZTCnk+ESLPe+LfSdAjqD6XkSSXviovOFIxUjASC80EwfeVo +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/puppetca/storage.yml b/test/fixtures/puppetca/storage.yml new file mode 100644 index 0000000000..40beb2a07d --- /dev/null +++ b/test/fixtures/puppetca/storage.yml @@ -0,0 +1,3 @@ +--- +- foo.example.com +- test.bar.example.com diff --git a/test/fixtures/puppetca/tokens.yml b/test/fixtures/puppetca/tokens.yml new file mode 100644 index 0000000000..2823469b12 --- /dev/null +++ b/test/fixtures/puppetca/tokens.yml @@ -0,0 +1,3 @@ +--- +- eyJhbGciOiJSUzUxMiJ9.eyJjZXJ0bmFtZSI6ImZvby5leGFtcGxlLmNvbSJ9.AYjLD3Oa7LG5y7IVxmQ0yDS3Lac7Jfk4qcOtnE56_ddzo46lsU-KDp3mdNMIPQVxyy-GAFuD-l9ed2vD4WKyqmLGQT7rPIdN9km9GjBGeCmlearPBZfCoIkRICFkys7Odmig-khshgZjPOL_mBrWPulsUOwXVmC0s5c5rrXRCg-HYvsDw1cI5Al-h4-nx9sI2VoVVXtAJ06GM5x10x0apDLU9FGT4sMQdkJNjwYaiDrTEELcuakJ1lGbpAAaiDZM6rFvuDIpC-6BIrEA6NGCUqSFcVnwpg3Foe6VhXs-whQlaKUfbslCHB4M1UnHRXmSFqPn3aCyyiVv4rQy9i4Cpg +- eyJhbGciOiJSUzUxMiJ9.eyJjZXJ0bmFtZSI6InRlc3QuYmFyLmV4YW1wbGUuY29tIn0.RrRmzCrzlp7Lgm1BDChv5zK8WyDIgYaVU0NTpoVS7xxhq3LFJAeJ6JrkhR69DJM9V5X1jAM9R0E5VeH4AkheCQLb-8UKwx2wmGZLWNXZq4fc40R_5NsHGKh9EiCHPXkwl4rz8AlJQmODKtBRe-J4xZ1jsc3qZZJA6L41L5aOr1N-dREQk8Jc6H9_mBXxawUEINZiAxH3F0zulz8vd-2b28BQRBhL-p2YaJtnRYJnxVd9n-GcxBJPnxsSN-dWj1Ttfr-P5iFPcqA1GV2-hfCjLiN8FdImnsJxuOIUhyiEhX6O9O6GQsv6S8vTnrBdxmRYAoe2x2fFRimtHAycGhNmIA diff --git a/test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner_test.rb b/test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner_test.rb index 0b62a6dc20..366476f09d 100644 --- a/test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner_test.rb +++ b/test/puppetca_hostname_whitelisting/puppetca_hostname_whitelisting_autosigner_test.rb @@ -29,7 +29,7 @@ def test_should_add_autosign_entry content = [] begin ## Execute - @autosigner.autosign 'foobar.example.com' + @autosigner.autosign 'foobar.example.com', 0 ## Read output content = @file.read.split("\n") ensure @@ -44,7 +44,7 @@ def test_should_not_duplicate_autosign_entry before_content = @file.read @file.seek(0) ## Execute - @autosigner.autosign 'foo.example.com' + @autosigner.autosign 'foo.example.com', 0 ## Read output after_content = @file.read ensure diff --git a/test/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner_test.rb b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner_test.rb new file mode 100644 index 0000000000..1930446b98 --- /dev/null +++ b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner_test.rb @@ -0,0 +1,98 @@ +require 'test_helper' +require 'tempfile' +require 'fileutils' +require 'openssl' +require 'yaml' +require 'json' + +require 'puppetca/puppetca' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_autosigner' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_csr' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage' + +class PuppetCaTokenWhitelistingAutosignerTest < Test::Unit::TestCase + def setup + @file = Tempfile.new('autosign_test') + begin + ## Setup + FileUtils.cp './test/fixtures/puppetca/tokens.yml', @file.path + rescue + @file.close + @file.unlink + @file = nil + end + @autosigner = Proxy::PuppetCa::TokenWhitelisting::Autosigner.new + @autosigner.stubs(:tokens_file).returns(@file.path) + rsa_cert = OpenSSL::PKey::RSA.new File.read './test/fixtures/puppetca/rsa_cert.pem' + @autosigner.stubs(:smartproxy_cert).returns(rsa_cert) + @autosigner.stubs(:token_ttl).returns(360) + end + + def teardown + @file.close + @file.unlink + end + + def test_should_list_autosign_entries + assert_equal @autosigner.autosign_list, ['foo.example.com', 'test.bar.example.com'] + end + + def test_should_add_autosign_entry + @autosigner.autosign 'foobar.example.com', 0 + assert_equal @autosigner.autosign_list, ['foo.example.com', 'test.bar.example.com', 'foobar.example.com'] + end + + def test_should_create_correct_token + response = @autosigner.autosign 'baz.example.com', 0 + token = JSON.parse(response)['generated_token'] + decoded = JWT.decode(token, @autosigner.smartproxy_cert.public_key, true, algorithm: 'RS512') + assert_equal decoded.first['certname'], 'baz.example.com' + assert 100 > (decoded.first['exp'] - Time.now.to_i - 360 * 60).abs + end + + def test_should_remove_autosign_entry + @autosigner.disable 'foo.example.com' + assert_equal @autosigner.autosign_list, ['test.bar.example.com'] + end + + def test_should_validate_on_sign_all + @autosigner.stubs(:sign_all).returns(true) + assert_true @autosigner.validate_csr '' + end + + def test_should_call_verification + csr_example = File.read './test/fixtures/puppetca/csr_example.pem' + @autosigner.expects(:validate_token).with('1234').returns(true) + assert_true @autosigner.validate_csr csr_example + end + + def test_should_validate_a_correct_token + response = @autosigner.autosign 'signme.example.com', 0 + token = JSON.parse(response)['generated_token'] + + assert_true @autosigner.validate_token token + end + + def test_should_not_validate_expired_token + payload = { certname: 'foo.example.com', exp: Time.now.to_i - 10 } + token = JWT.encode payload, @autosigner.smartproxy_cert, 'RS512' + + assert_false @autosigner.validate_token token + end + + def test_should_not_validate_token_with_invalid_certname + payload = { certname: 'unknown.example.com', exp: Time.now.to_i + 999_999 } + token = JWT.encode payload, @autosigner.smartproxy_cert, 'RS512' + + assert_false @autosigner.validate_token token + end + + def test_should_not_validate_token_with_unkown_signature + unknown_cert = OpenSSL::PKey::RSA.generate 2048 + payload = { certname: 'foo.example.com', exp: Time.now.to_i + 999_999 } + token = JWT.encode payload, unknown_cert, 'RS512' + + assert_false @autosigner.validate_token token + end +end diff --git a/test/puppetca_token_whitelisting/puppetca_token_whitelisting_config_test.rb b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_config_test.rb new file mode 100644 index 0000000000..5cc3ac441e --- /dev/null +++ b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_config_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' +require 'puppetca/puppetca' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_plugin' + +class PuppetCATokenWhitelistingConfigTest < Test::Unit::TestCase + def test_omitted_settings_have_default_values + Proxy::PuppetCa::TokenWhitelisting::Plugin.load_test_settings({}) + assert_equal false, Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.sign_all + assert_equal '/var/lib/foreman-proxy/tokens.yml', Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.tokens_file + assert_equal 360, Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.token_ttl + assert_equal nil, Proxy::PuppetCa::TokenWhitelisting::Plugin.settings.certificate + end +end diff --git a/test/puppetca_token_whitelisting/puppetca_token_whitelisting_csr_test.rb b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_csr_test.rb new file mode 100644 index 0000000000..7f32f4547e --- /dev/null +++ b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_csr_test.rb @@ -0,0 +1,23 @@ +require 'test_helper' + +require 'puppetca/puppetca' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_csr' + +class PuppetCaTokenWhitelistingCSRTest < Test::Unit::TestCase + def setup + @csr_example = File.read './test/fixtures/puppetca/csr_example.pem' + end + + def test_should_extract_correct_attribute + req = Proxy::PuppetCa::TokenWhitelisting::CSR.new @csr_example + assert_equal '1234', req.challenge_password + end + + def test_should_fail_on_invalid_csr + @csr_example.slice!(42...69) + assert_raise OpenSSL::X509::RequestError do + Proxy::PuppetCa::TokenWhitelisting::CSR.new @csr_example + end + end +end diff --git a/test/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage_test.rb b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage_test.rb new file mode 100644 index 0000000000..372e410230 --- /dev/null +++ b/test/puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage_test.rb @@ -0,0 +1,56 @@ +require 'test_helper' + +require 'timeout' +require 'puppetca/puppetca' +require 'puppetca_token_whitelisting/puppetca_token_whitelisting_token_storage' + +class PuppetCaTokenWhitelistingTokenStorageTest < Test::Unit::TestCase + def setup + @file = Tempfile.new('autosign_test') + begin + ## Setup + FileUtils.cp './test/fixtures/puppetca/storage.yml', @file.path + rescue + @file.close + @file.unlink + @file = nil + end + @storage = Proxy::PuppetCa::TokenWhitelisting::TokenStorage.new @file.path + end + + def teardown + @file.close + @file.unlink + end + + def test_should_be_able_to_read_file + result = @storage.read + assert_equal ['foo.example.com', 'test.bar.example.com'], result + end + + def test_should_be_able_to_write_file + data = ['42.foo.example.com', 'baz.example.com'] + @storage.write data + assert_equal data, @storage.read + end + + def test_should_be_able_to_add_elements + @storage.add 'baz.example.com' + assert_equal ['foo.example.com', 'test.bar.example.com', 'baz.example.com'], @storage.read + end + + def test_should_be_able_to_remove_elements + @storage.remove 'foo.example.com' + assert_equal ['test.bar.example.com'], @storage.read + end + + def test_should_queue_writes_when_locked + @storage.lock do + assert_raise Timeout::Error do + Timeout::timeout(3) do + @storage.write ['test'] + end + end + end + end +end