diff --git a/lib/cloud/cloud.rb b/lib/cloud/cloud.rb index af2d06e..d91b1be 100644 --- a/lib/cloud/cloud.rb +++ b/lib/cloud/cloud.rb @@ -2,13 +2,15 @@ require 'utils/utils' class Vcert::CloudConnection - def initialize(url, token) - if url == nil - @url = "https://api.venafi.cloud/v1" - else - @url = url - end - @token = token + CLOUD_PREFIX = ''.freeze + + def initialize(url, apikey) + @url = if url.nil? + 'https://api.venafi.cloud/v1'.freeze + else + url + end + @apikey = apikey end @@ -178,10 +180,11 @@ def get(url) url = uri.path + "/" + url - response = request.get(url, {TOKEN_HEADER_NAME => @token}) + LOG.info("#{CLOUD_PREFIX} GET #{url}") + response = request.get(url, { TOKEN_HEADER_NAME => @apikey }) case response.code.to_i when 200, 201, 202, 409 - LOG.info(("HTTP status OK")) + LOG.info("#{CLOUD_PREFIX} GET HTTP status OK") when 403 raise Vcert::AuthenticationError else @@ -210,10 +213,11 @@ def post(url, data) request.use_ssl = true url = uri.path + "/" + url encoded_data = JSON.generate(data) - response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token, "Content-Type" => "application/json", "Accept" => "application/json"}) + LOG.info("#{CLOUD_PREFIX} POST #{url}") + response = request.post(url, encoded_data, { TOKEN_HEADER_NAME => @apikey, "Content-Type" => "application/json", "Accept" => "application/json" }) case response.code.to_i when 200, 201, 202, 409 - LOG.info(("HTTP status OK")) + LOG.info("#{CLOUD_PREFIX} POST HTTP status OK") when 403 raise Vcert::AuthenticationError else diff --git a/lib/objects/objects.rb b/lib/objects/objects.rb index d2f1676..1c7696c 100644 --- a/lib/objects/objects.rb +++ b/lib/objects/objects.rb @@ -58,7 +58,7 @@ def generate_csr subject_attrs.push(['L', @locality]) end - LOG.info("Making request from subject array #{subject_attrs.inspect}") + LOG.info("#{VCERT_PREFIX} Making request from subject array #{subject_attrs.inspect}") subject = OpenSSL::X509::Name.new subject_attrs csr = OpenSSL::X509::Request.new csr.version = 0 diff --git a/lib/tpp/tpp.rb b/lib/tpp/tpp.rb index 66e04f8..47e21a1 100644 --- a/lib/tpp/tpp.rb +++ b/lib/tpp/tpp.rb @@ -55,43 +55,36 @@ def zone_configuration(zone_tag) end def renew(request, generate_new_key: true) - if request.id == nil && request.thumbprint == nil - raise("Either request ID or certificate thumbprint is required to renew the certificate") + if request.id.nil? && request.thumbprint.nil? + raise('Either request ID or certificate thumbprint is required to renew the certificate') end - if request.thumbprint != nil - request.id = search_by_thumbprint(request.thumbprint) - end + request.id = search_by_thumbprint(request.thumbprint) unless request.thumbprint.nil? renew_req_data = {"CertificateDN": request.id} if generate_new_key - _, r = post(URL_SECRET_STORE_SEARCH, d = {"Namespace": "config", "Owner": request.id, "VaultType": 512}) - vaultId = r["VaultIDs"][0] - _, r = post(URL_SECRET_STORE_RETRIEVE, d = {"VaultID": vaultId}) - csr_base64_data = r['Base64Data'] - csr_pem = "-----BEGIN CERTIFICATE REQUEST-----\n#{csr_base64_data}\n-----END CERTIFICATE REQUEST-----\n" - parsed_csr = parse_csr_fields(csr_pem) + csr_base64_data = retrieve request + LOG.info("Retrieved certificate:\n#{csr_base64_data.cert}") + parsed_csr = parse_csr_fields_tpp(csr_base64_data.cert) renew_request = Vcert::Request.new( - common_name: parsed_csr.fetch(:CN, nil), - san_dns: parsed_csr.fetch(:DNS, nil), - country: parsed_csr.fetch(:C, nil), - province: parsed_csr.fetch(:ST, nil), - locality: parsed_csr.fetch(:L, nil), - organization: parsed_csr.fetch(:O, nil), - organizational_unit: parsed_csr.fetch(:OU, nil)) + common_name: parsed_csr.fetch(:CN, nil), + san_dns: parsed_csr.fetch(:DNS, nil), + country: parsed_csr.fetch(:C, nil), + province: parsed_csr.fetch(:ST, nil), + locality: parsed_csr.fetch(:L, nil), + organization: parsed_csr.fetch(:O, nil), + organizational_unit: parsed_csr.fetch(:OU, nil) + ) renew_req_data.merge!(PKCS10: renew_request.csr) end - LOG.info("Trying to renew certificate %s" % request.id) + LOG.info("Trying to renew certificate #{request.id}") _, d = post(URL_CERTIFICATE_RENEW, renew_req_data) - if d.key?('Success') - if generate_new_key - return request.id, renew_request.private_key - else - return request.id, nil - end + raise 'Certificate renew error' unless d.key?('Success') + + if generate_new_key + [request.id, renew_request.private_key] else - raise "Certificate renew error" + [request.id, nil] end - end private @@ -140,6 +133,7 @@ def post(url, data) end url = uri.path + url encoded_data = JSON.generate(data) + LOG.info("#{Vcert::VCERT_PREFIX} POST request: #{request.inspect}\n\tpath: #{url}\n\tdata: #{encoded_data}") response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token[0], "Content-Type" => "application/json"}) data = JSON.parse(response.body) return response.code.to_i, data @@ -156,7 +150,8 @@ def get(url) request.ca_file = @trust_bundle end url = uri.path + url - response = request.get(url, {TOKEN_HEADER_NAME => @token[0]}) + LOG.info("#{Vcert::VCERT_PREFIX} GET request: #{request.inspect}\n\tpath: #{url}") + response = request.get(url, { TOKEN_HEADER_NAME => @token[0] }) # TODO: check valid json data = JSON.parse(response.body) return response.code.to_i, data diff --git a/lib/tpp/tpp_token.rb b/lib/tpp/tpp_token.rb new file mode 100644 index 0000000..d584143 --- /dev/null +++ b/lib/tpp/tpp_token.rb @@ -0,0 +1,430 @@ +require 'json' +require 'date' +require 'base64' +require 'utils/utils' + + +class Vcert::TokenConnection + + # @param [String] url + # @param [String] access_token + # @param [String] refresh_token + # @param [String] user + # @param [String] password + # @param [String] trust_bundle + def initialize(url, access_token: nil, refresh_token: nil, user: nil, password: nil , trust_bundle: nil) + @url = normalize_url url + @auth = Vcert::Authentication.new access_token: access_token, refresh_token: refresh_token, user: user, password: password + @trust_bundle = trust_bundle + end + + # @param [String] zone_tag + # @param [Vcert::Request] request + def request(zone_tag, request) + data = { PolicyDN: policy_dn(zone_tag), + PKCS10: request.csr, + ObjectName: request.friendly_name, + DisableAutomaticRenewal: 'true' } + code, response = post URL_CERTIFICATE_REQUESTS, data + raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200 + + request.id = response['CertificateDN'] + end + + # @param [Request] request + # @return [Vcert::Certificate] + def retrieve(request) + retrieve_request = { CertificateDN: request.id, Format: 'base64', IncludeChain: 'true', RootFirstOrder: 'false' } + code, response = post URL_CERTIFICATE_RETRIEVE, retrieve_request + return nil if code != 200 + + full_chain = Base64.decode64(response['CertificateData']) + LOG.info("#{Vcert::VCERT_PREFIX} cert data decoded: #{full_chain}") + cert = parse_full_chain full_chain + cert.private_key = request.private_key if cert.private_key == nil + cert + end + + # @param [String] zone_tag + # @return [Vcert::Policy] + def policy(zone_tag) + code, response = post URL_ZONE_CONFIG, { PolicyDN: policy_dn(zone_tag) } + raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200 + + parse_policy_response response, zone_tag + end + + # @param [String] zone_tag + # @return [Vcert::ZoneConfiguration] + def zone_configuration(zone_tag) + LOG.info("#{Vcert::VCERT_PREFIX} Reading zone configuration: #{zone_tag}") + code, response = post URL_ZONE_CONFIG, { PolicyDN: policy_dn(zone_tag) } + raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200 + + parse_zone_configuration response + end + + def renew(request, generate_new_key: true) + if request.id.nil? && request.thumbprint.nil? + raise('Either request ID or certificate thumbprint is required to renew the certificate') + end + + request.id = search_by_thumbprint(request.thumbprint) unless request.thumbprint.nil? + renew_req_data = { CertificateDN: request.id } + if generate_new_key + csr_base64_data = retrieve request + LOG.info("#{Vcert::VCERT_PREFIX} Retrieved certificate:\n#{csr_base64_data.cert}") + parsed_csr = parse_csr_fields_tpp(csr_base64_data.cert) + renew_request = Vcert::Request.new( + common_name: parsed_csr.fetch(:CN, nil), + san_dns: parsed_csr.fetch(:DNS, nil), + country: parsed_csr.fetch(:C, nil), + province: parsed_csr.fetch(:ST, nil), + locality: parsed_csr.fetch(:L, nil), + organization: parsed_csr.fetch(:O, nil), + organizational_unit: parsed_csr.fetch(:OU, nil) + ) + renew_req_data.merge!(PKCS10: renew_request.csr) + end + LOG.info("#{Vcert::VCERT_PREFIX} Trying to renew certificate #{request.id}") + _, d = post(URL_CERTIFICATE_RENEW, renew_req_data) + raise 'Certificate renew error' unless d.key?('Success') + + if generate_new_key + [request.id, renew_request.private_key] + else + [request.id, nil] + end + end + + # @param [Vcert::Authentication] authentication + # @return [Vcert::TokenInfo] + def get_access_token(authentication: nil) + @auth = authentication unless authentication.nil? + return refresh_access_token unless @auth.refresh_token.nil? + + return nil if @auth.user.nil? || @auth.password.nil? + + request_data = { + username: @auth.user, + password: @auth.password, + client_id: @auth.client_id, + scope: @auth.scope, + state: '' + } + status, response = post(URL_AUTHORIZE_TOKEN, request_data, check_token: false, include_headers: false) + raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if status != 200 + + token_info = parse_access_token_data response + update_authentication(token_info) + token_info + end + + # @return [Vcert::TokenInfo] + def refresh_access_token + request_data = { + refresh_token: @auth.refresh_token, + client_id: @auth.client_id + } + + status, response = post(URL_REFRESH_TOKEN, request_data, check_token: false, include_headers: false) + if status != 200 + raise Vcert::ServerUnexpectedBehaviorError, "Server returns #{code} status on refreshing access token" + end + + token_info = parse_access_token_data(response) + update_authentication(token_info) + token_info + end + + # @return [] + def revoke_access_token + status, response = get(URL_REVOKE_TOKEN, check_token: false) + if status != 200 + raise Vcert::ServerUnexpectedBehaviorError, "Server returns #{status} status on revoking access token" + end + + response + end + + private + + API_TOKEN_URL = 'vedauth/'.freeze + API_BASE_URL = 'vedsdk/'.freeze + + URL_AUTHORIZE_TOKEN = "#{API_TOKEN_URL}authorize/oauth".freeze + URL_REFRESH_TOKEN = "#{API_TOKEN_URL}authorize/token".freeze + URL_REVOKE_TOKEN = "#{API_TOKEN_URL}revoke/token".freeze + + URL_AUTHORIZE = "#{API_BASE_URL}authorize/".freeze + URL_CERTIFICATE_REQUESTS = "#{API_BASE_URL}certificates/request".freeze + URL_ZONE_CONFIG = "#{API_BASE_URL}certificates/checkpolicy".freeze + URL_CERTIFICATE_RETRIEVE = "#{API_BASE_URL}certificates/retrieve".freeze + URL_CERTIFICATE_SEARCH = "#{API_BASE_URL}certificates/".freeze + URL_CERTIFICATE_RENEW = "#{API_BASE_URL}certificates/renew".freeze + URL_SECRET_STORE_SEARCH = "#{API_BASE_URL}SecretStore/LookupByOwner".freeze + URL_SECRET_STORE_RETRIEVE = "#{API_BASE_URL}SecretStore/Retrieve".freeze + HEADER_NAME_AUTHORIZATION = 'Authorization'.freeze + ALL_ALLOWED_REGEX = '.*'.freeze + + # @param [String] url + # @param [Hash] data + # @param [boolean] check_token + # @param [boolean] include_headers + # @return [Integer, Array] + def post(url, data, check_token: true, include_headers: true) + validate_token if check_token + + uri = URI.parse(@url) + request = Net::HTTP.new(uri.host, uri.port) + request.use_ssl = true + request.ca_file = @trust_bundle unless @trust_bundle.nil? + url = uri.path + url + encoded_data = JSON.generate(data) + headers = { + 'Content-Type' => 'application/json' + } + headers.merge!(HEADER_NAME_AUTHORIZATION => build_authorization_header_value) if include_headers + LOG.info("#{Vcert::VCERT_PREFIX} POST request: #{request.inspect}\n\tpath: #{url}\n\tdata: #{encoded_data}\n\theaders: #{headers}") + response = request.post(url, encoded_data, headers) + LOG.info("#{Vcert::VCERT_PREFIX} POST response: [#{response.body}]") + data = JSON.parse(response.body) + [response.code.to_i, data] + end + + # @param [String] url + # @param [boolean] check_token + # @param [boolean] include_headers + # @return [Integer, Array] + def get(url, check_token: true, include_headers: true) + validate_token if check_token + + uri = URI.parse(@url) + request = Net::HTTP.new(uri.host, uri.port) + request.use_ssl = true + request.ca_file = @trust_bundle unless @trust_bundle.nil? + + url = uri.path + url + + headers = {} + headers = { HEADER_NAME_AUTHORIZATION => build_authorization_header_value } if include_headers + LOG.info("#{Vcert::VCERT_PREFIX} GET request: #{request.inspect}\n\tpath: #{url}\n\theaders: [#{headers}]") + response = request.get(url, headers) + LOG.info("#{Vcert::VCERT_PREFIX} GET response with status [#{response.code}]. #{response.inspect} ") + # TODO: check valid json + data = JSON.parse(response.body) unless response.body.nil? || response.body.eql?('') + [response.code.to_i, data] + end + + # @param [String] zone + # @return [String] + def policy_dn(zone) + raise Vcert::ClientBadDataError, 'Zone should not be empty' if zone.nil? || zone == '' + return zone if zone =~ /^\\\\VED\\\\Policy/ + + if zone =~ /^\\\\/ + "\\VED\\Policy#{zone}" + else + "\\VED\\Policy\\#{zone}" + end + end + + # @param [String] url + # @return [String] + def normalize_url(url) + if url.index('http://') == 0 + url = "https://#{url[7..-1]}" + elsif url.index('https://') != 0 + url = "https://#{url}" + end + url += '/' unless url.end_with?('/') + raise Vcert::ClientBadDataError, 'Invalid URL for TPP' unless url =~ %r{^https://[a-z\d]+[-a-z\d.]+[a-z\d][:\d]*/$} + + url + end + + # @param [String] full_chain + # @return [Vcert::Certificate] + def parse_full_chain(full_chain) + pem_list = parse_pem_list(full_chain) + Vcert::Certificate.new cert: pem_list[0], chain: pem_list[1..-1], private_key: nil + end + + # @param [String] thumbprint + # @return [String] + def search_by_thumbprint(thumbprint) + # thumbprint = re.sub(r'[^\dabcdefABCDEF]', "", thumbprint) + thumbprint = thumbprint.upcase + status, data = get(URL_CERTIFICATE_SEARCH + "?Thumbprint=#{thumbprint}") + # TODO: check that data have valid certificate in it + raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status}. Message:\n #{data.body.to_s}" if status != 200 + + # TODO: check valid data + data['Certificates'][0]['DN'] + end + + # @param [Hash] data + # @return [Vcert::ZoneConfiguration] + def parse_zone_configuration(data) + LOG.info("#{Vcert::VCERT_PREFIX} Parsing Zone configuration: #{data}") + s = data['Policy']['Subject'] + country = Vcert::CertField.new s['Country']['Value'], locked: s['Country']['Locked'] + state = Vcert::CertField.new s['State']['Value'], locked: s['State']['Locked'] + city = Vcert::CertField.new s['City']['Value'], locked: s['City']['Locked'] + organization = Vcert::CertField.new s['Organization']['Value'], locked: s['Organization']['Locked'] + organizational_unit = Vcert::CertField.new s['OrganizationalUnit']['Values'], locked: s['OrganizationalUnit']['Locked'] + key_type = Vcert::KeyType.new data['Policy']['KeyPair']['KeyAlgorithm']['Value'], data['Policy']['KeyPair']['KeySize']['Value'] + Vcert::ZoneConfiguration.new country: country, province: state, locality: city, organization: organization, + organizational_unit: organizational_unit, key_type: Vcert::CertField.new(key_type) + end + + # @param [Hash] response + # @param [String] zone_tag + # @return [Vcert::Policy] + def parse_policy_response(response, zone_tag) + def addStartEnd(s) + s = '^' + s unless s.index('^') == 0 + s = s + '$' unless s.end_with?('$') + s + end + + def escape(value) + if value.kind_of? Array + return value.map { |v| addStartEnd(Regexp.escape(v)) } + else + return addStartEnd(Regexp.escape(value)) + end + end + + policy = response['Policy'] + s = policy['Subject'] + if policy['WhitelistedDomains'].empty? + subjectCNRegex = [ALL_ALLOWED_REGEX] + else + if policy['WildcardsAllowed'] + subjectCNRegex = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w\-*]+' + Regexp.escape('.' + d)) } + else + subjectCNRegex = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w\-]+' + Regexp.escape('.' + d)) } + end + end + if s['OrganizationalUnit']['Locked'] + subjectOURegexes = escape(s['OrganizationalUnit']['Values']) + else + subjectOURegexes = [ALL_ALLOWED_REGEX] + end + if s['Organization']['Locked'] + subjectORegexes = [escape(s['Organization']['Value'])] + else + subjectORegexes = [ALL_ALLOWED_REGEX] + end + if s['City']['Locked'] + subjectLRegexes = [escape(s['City']['Value'])] + else + subjectLRegexes = [ALL_ALLOWED_REGEX] + end + if s['State']['Locked'] + subjectSTRegexes = [escape(s['State']['Value'])] + else + subjectSTRegexes = [ALL_ALLOWED_REGEX] + end + if s['Country']['Locked'] + subjectCRegexes = [escape(s['Country']['Value'])] + else + subjectCRegexes = [ALL_ALLOWED_REGEX] + end + if policy['SubjAltNameDnsAllowed'] + if policy['WhitelistedDomains'].length == 0 + dnsSanRegExs = [ALL_ALLOWED_REGEX] + else + dnsSanRegExs = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w-]+' + Regexp.escape('.' + d)) } + end + else + dnsSanRegExs = [] + end + if policy['SubjAltNameIpAllowed'] + ipSanRegExs = [ALL_ALLOWED_REGEX] # todo: support + else + ipSanRegExs = [] + end + if policy['SubjAltNameEmailAllowed'] + emailSanRegExs = [ALL_ALLOWED_REGEX] # todo: support + else + emailSanRegExs = [] + end + if policy['SubjAltNameUriAllowed'] + uriSanRegExs = [ALL_ALLOWED_REGEX] # todo: support + else + uriSanRegExs = [] + end + + if policy['SubjAltNameUpnAllowed'] + upnSanRegExs = [ALL_ALLOWED_REGEX] # todo: support + else + upnSanRegExs = [] + end + unless policy['KeyPair']['KeyAlgorithm']['Locked'] + key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new('rsa', s) } + Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new('ecdsa', c) } + else + if policy['KeyPair']['KeyAlgorithm']['Value'] == 'RSA' + if policy['KeyPair']['KeySize']['Locked'] + key_types = [Vcert::KeyType.new('rsa', policy['KeyPair']['KeySize']['Value'])] + else + key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new('rsa', s) } + end + elsif policy['KeyPair']['KeyAlgorithm']['Value'] == 'EC' + if policy['KeyPair']['EllipticCurve']['Locked'] + curve = { 'p224' => 'secp224r1', 'p256' => 'prime256v1', 'p521' => 'secp521r1' }[policy['KeyPair']['EllipticCurve']['Value'].downcase] + key_types = [Vcert::KeyType.new('ecdsa', curve)] + else + key_types = Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new('ecdsa', c) } + end + end + end + + Vcert::Policy.new(policy_id: policy_dn(zone_tag), name: zone_tag, system_generated: false, creation_date: nil, + subject_cn_regexes: subjectCNRegex, subject_o_regexes: subjectORegexes, + subject_ou_regexes: subjectOURegexes, subject_st_regexes: subjectSTRegexes, + subject_l_regexes: subjectLRegexes, subject_c_regexes: subjectCRegexes, san_regexes: dnsSanRegExs, + key_types: key_types) + end + + # @param [Hash] response_data + # @return [Vcert::TokenInfo] + def parse_access_token_data(response_data) + Vcert::TokenInfo.new response_data['access_token'], + response_data['expires'], + response_data['identity'], + response_data['refresh_token'], + response_data['refresh_until'], + response_data['scope'], + response_data['token_type'] + end + + # @param [Vcert::TokenInfo] token_info + def update_authentication(token_info) + return unless token_info.instance_of?(Vcert::TokenInfo) + + @auth.access_token = token_info.access_token + @auth.refresh_token = token_info.refresh_token + @auth.token_expiration_date = token_info.expires + end + + def validate_token + if @auth.access_token.nil? + LOG.info("#{Vcert::VCERT_PREFIX} Requesting new Access Token with credentials.") + get_access_token + elsif !@auth.token_expiration_date.nil? && @auth.token_expiration_date < Time.now.to_i + raise Vcert::AuthenticationError, 'Access Token expired. No refresh token provided.' if @auth.refresh_token.nil? + + LOG.info("#{Vcert::VCERT_PREFIX} Requesting new Access Token with refresh token.") + refresh_access_token + end + end + + # @return [String] + def build_authorization_header_value + return "Bearer #{@auth.access_token}" unless @auth.access_token.nil? + end +end + + diff --git a/lib/utils/utils.rb b/lib/utils/utils.rb index 948b5b0..439827d 100644 --- a/lib/utils/utils.rb +++ b/lib/utils/utils.rb @@ -19,16 +19,16 @@ def parse_pem_list(multiline) end def parse_csr_fields(csr) - LOG.info("Trying to parse CSR:\n#{csr}") + LOG.info("#{Vcert::VCERT_PREFIX} Trying to parse CSR:\n#{csr}") csr_obj = OpenSSL::X509::Request.new(csr) result = Hash.new subject_array = csr_obj.subject.to_a - subject_array.map { |x| + subject_array.map do |x| if x[1] != "" result[x[0].to_sym] = x[1] end - } + end attributes = csr_obj.attributes @@ -93,6 +93,54 @@ def parse_csr_fields(csr) end - LOG.info("Parsed CSR fields:\n #{result.inspect}") + LOG.info("#{Vcert::VCERT_PREFIX} Parsed CSR fields:\n #{result.inspect}") return result end + +def parse_csr_fields_tpp(csr) + LOG.info("#{Vcert::VCERT_PREFIX} Trying to parse CSR:\n#{csr}") + csr_obj = OpenSSL::X509::Certificate.new(csr) + result = Hash.new + + subject_array = csr_obj.subject.to_a + subject_array.map do |x| + result[x[0].to_sym] = x[1] unless x[1] == '' + end + + LOG.info("#{Vcert::VCERT_PREFIX} Parsed CSR fields:\n #{result.inspect}") + result +end + +CLIENT_ID = 'vcert-sdk'.freeze +SCOPE = 'certificate:manage,revoke'.freeze + +module Vcert + class Authentication + attr_accessor :access_token, :refresh_token, :user, :password, :token_expiration_date, :client_id, :scope + + def initialize (access_token: nil, refresh_token: nil, user: nil, password: nil, expiration_date: nil, client_id: CLIENT_ID, scope: SCOPE) + @access_token = access_token + @refresh_token = refresh_token + @user = user + @password = password + @token_expiration_date = expiration_date + @client_id = client_id + @scope = scope + end + end + + class TokenInfo + attr_reader :access_token, :refresh_token, :refresh_until, :expires, :identity, :scope, :token_type + + def initialize (access_token, expires, identity, refresh_token, refresh_until, scope, token_type) + @access_token = access_token + @refresh_token = refresh_token + @refresh_until = refresh_until + @expires = expires + @identity = identity + @scope = scope + @token_type = token_type + end + end +end + diff --git a/lib/vcert.rb b/lib/vcert.rb index d47f4dc..b8559b8 100644 --- a/lib/vcert.rb +++ b/lib/vcert.rb @@ -10,16 +10,28 @@ class ServerUnexpectedBehaviorError < VcertError; end class ClientBadDataError < VcertError; end class ValidationError < VcertError; end + VCERT_PREFIX = ''.freeze + + # DEPRECATED + # Please use VenafiConnection instead. + # + # This class provides an easy way to configure and retrieve a connector for a Venafi platform. + # Usage: + # TPP: + # Connection.new url: TPP_URL, user: TPP_USER, password: TPP_PASSWORD, trust_bundle: TRUST_BUNDLE + # CLoud: + # Connection.new token: CLOUD_API_KEY + # class Connection def initialize(url: nil, user: nil, password: nil, cloud_token: nil, trust_bundle:nil, fake: false) if fake @conn = FakeConnection.new - elsif cloud_token != nil + elsif !cloud_token.nil? @conn = CloudConnection.new url, cloud_token - elsif user != nil && password != nil && url != nil then + elsif !user.nil? && !password.nil? && !url.nil? @conn = TPPConnection.new url, user, password, trust_bundle:trust_bundle else - raise ClientBadDataError, "Invalid credentials list" + raise ClientBadDataError, 'Invalid credentials list' end end @@ -61,31 +73,73 @@ def renew(*args) # @param [Integer] timeout # @return [Certificate] def request_and_retrieve(req, zone, timeout: TIMEOUT) + LOG.info("#{VCERT_PREFIX} Requesting and retrieving Certificate: [#{req}], [#{zone}]") request zone, req - cert = retrieve_loop(req, timeout: timeout) - return cert + retrieve_loop(req, timeout: timeout) end def retrieve_loop(req, timeout: TIMEOUT) - t = Time.new() + timeout + t = Time.new + timeout loop do - if Time.new() > t - LOG.info("Waiting certificate #{req.id}") + if Time.new > t + LOG.info("#{VCERT_PREFIX} Waiting certificate #{req.id}") break end certificate = @conn.retrieve(req) - if certificate != nil - return certificate - end + return certificate unless certificate.nil? sleep 10 end - return nil + nil end end + + # This class provides an easy way to configure and retrieve a connector for a Venafi platform. + # It supports the use of token authentication for TPP, and drops the use of user/password credentials. + # Usage: + # TPP: + # VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE + # VenafiConnection.new url: TPP_TOKEN_URL, access_token: TPP_ACCESS_TOKEN, trust_bundle: TRUST_BUNDLE + # VenafiConnection.new url: TPP_TOKEN_URL, refresh_token: TPP_REFRESH_TOKEN, trust_bundle: TRUST_BUNDLE + # CLoud: + # VenafiConnection.new token: CLOUD_API_KEY + # + class VenafiConnection < Connection + + def initialize(url: nil, access_token: nil, refresh_token: nil, user: nil, password: nil, apikey: nil, trust_bundle:nil, fake: false) + if fake + @conn = FakeConnection.new + elsif !apikey.nil? + @conn = CloudConnection.new url, apikey + elsif (!access_token.nil? || !refresh_token.nil? || (!user.nil? && !password.nil?)) && !url.nil? + @conn = TokenConnection.new url, access_token: access_token, refresh_token: refresh_token, user: user, + password: password, trust_bundle: trust_bundle + else + raise ClientBadDataError, 'Invalid credentials list' + end + end + + # @param [Vcert::Authentication] authentication + # @return [Vcert::TokenInfo] + def get_access_token(authentication: nil) + @conn.get_access_token authentication: authentication if @conn.is_a?(Vcert::TokenConnection) + end + + # @return [Vcert::TokenInfo] + def refresh_access_token + @conn.refresh_access_token if @conn.is_a?(Vcert::TokenConnection) + end + + # @return [] + def revoke_access_token + @conn.revoke_access_token if @conn.is_a?(Vcert::TokenConnection) + end + end end require 'fake/fake' require 'cloud/cloud' require 'tpp/tpp' -require 'objects/objects' \ No newline at end of file +require 'tpp/tpp_token' +require 'objects/objects' + diff --git a/tests/test_vcert.rb b/tests/test_vcert.rb index 8b8af6e..e67221b 100644 --- a/tests/test_vcert.rb +++ b/tests/test_vcert.rb @@ -1,4 +1,4 @@ -require "test/unit/assertions" +require 'test/unit/assertions' require 'minitest/autorun' require 'vcert' require 'openssl' @@ -10,9 +10,10 @@ TPPURL = ENV['TPP_URL'] TPPUSER = ENV['TPP_USER'] TPPPASSWORD = ENV['TPP_PASSWORD'] -TPPZONE = ENV["TPP_ZONE"] -TRUST_BUNDLE = ENV["TRUST_BUNDLE"] -CSR_TEST = "-----BEGIN CERTIFICATE REQUEST----- +TPPZONE = ENV['TPP_ZONE'] +TRUST_BUNDLE = ENV['TRUST_BUNDLE'] +TPP_TOKEN_URL = ENV['TPP_TOKEN_URL'] +CSR_TEST = '-----BEGIN CERTIFICATE REQUEST----- MIIC5TCCAc0CAQAwdzELMAkGA1UEBhMCVVMxDTALBgNVBAgMBFV0YWgxFzAVBgNV BAcMDlNhbHQgTGFrZSBDaXR5MQ8wDQYDVQQKDAZWZW5hZmkxFDASBgNVBAsMC0lu dGVncmF0aW9uMRkwFwYDVQQDDBB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG @@ -29,15 +30,15 @@ AilzNOkXEeBwCT79bdpc3xh/hrjf9PeItLMpS7lVUUYQH18JK203BMGOE76EaELA fk2X1wGedpdby5XRW0a7qozvwdBBTfI6/yMTP+iF5ghzvpCGtX2tYkyQ0I2GT/hV YuWiOhL8NVOxPWFbiKWghQ2qH3hE0arsDA== ------END CERTIFICATE REQUEST-----" -TEST_DOMAIN = "example.com" +-----END CERTIFICATE REQUEST-----'.freeze +TEST_DOMAIN = 'example.com'.freeze def random_string(length) Array.new(length) { Array('a'..'z').sample }.join end def random_domain - random_string(10) + "." + TEST_DOMAIN + "#{random_string(10)}.#{TEST_DOMAIN}" end class VcertTest < Minitest::Test @@ -45,14 +46,14 @@ class VcertTest < Minitest::Test def request_and_renew(conn, zone) cn = random_domain LOG.info("Requesting cert with CN #{cn}") - kt = Vcert::KeyType.new("rsa", 4096) - request = Vcert::Request.new(common_name: cn, country: "US", key_type: kt, san_dns: ["ext-"+cn]) + kt = Vcert::KeyType.new('rsa', 4096) + request = Vcert::Request.new(common_name: cn, country: 'US', key_type: kt, san_dns: ['ext-'+cn]) zone_config = conn.zone_configuration(zone) request.update_from_zone_config(zone_config) cert = conn.request_and_retrieve(request, zone, timeout: 300) - assert (cert.cert != nil) - LOG.info(("cert is:\n" + cert.cert)) - LOG.info(("pk is:\n" + cert.private_key)) + assert (!cert.cert.nil?) + LOG.info(("cert is:\n#{cert.cert}")) + LOG.info(("pk is:\n#{cert.private_key}")) certificate_object = OpenSSL::X509::Certificate.new(cert.cert) key_object = OpenSSL::PKey::RSA.new(cert.private_key) @@ -66,15 +67,16 @@ def request_and_renew(conn, zone) renew_request.id = renew_cert_id sleep(5) renew_cert = conn.retrieve_loop(renew_request) - LOG.info(("renewd cert is:\n" + renew_cert.cert)) - LOG.info(("renewd cert key is:\n" + renew_private_key)) - assert (renew_cert.cert != nil ) + LOG.info(("renewed cert is:\n#{renew_cert.cert}")) + LOG.info(("renewed cert key is:\n#{renew_private_key}")) + assert (!renew_cert.cert.nil?) renew_certificate_object = OpenSSL::X509::Certificate.new(renew_cert.cert) - assert !renew_certificate_object.check_private_key(key_object), "Renewed cert signed by same key" + assert !renew_certificate_object.check_private_key(key_object), 'Renewed cert signed by same key' renew_key_object = OpenSSL::PKey::RSA.new(renew_private_key) - assert renew_certificate_object.check_private_key(renew_key_object), "Renewed cert signed by the wrong key" - assert (certificate_object.serial != renew_certificate_object.serial), "Original cert sn and renew sn are equal" - assert (certificate_object.subject.to_a.select{|name, _, _| name == 'CN' }.first[1] == renew_certificate_object.subject.to_a.select{|name, _, _| name == 'CN' }.first[1]) + assert renew_certificate_object.check_private_key(renew_key_object), 'Renewed cert signed by the wrong key' + assert (certificate_object.serial != renew_certificate_object.serial), 'Original cert sn and renew sn are equal' + assert (certificate_object.subject.to_a.select { |name, _, _| name == 'CN' }.first[1] == + renew_certificate_object.subject.to_a.select { |name, _, _| name == 'CN' }.first[1]) sleep(5) #Search by thumbprint test thumbprint = OpenSSL::Digest::SHA1.new(renew_certificate_object.to_der).to_s @@ -85,15 +87,16 @@ def request_and_renew(conn, zone) sleep(5) thumbprint_renew_request.id=thumbprint_renew_cert_id thumbprint_renew_cert = conn.retrieve_loop(thumbprint_renew_request) - LOG.info(("thumbprint renewd cert is:\n" + thumbprint_renew_cert.cert)) - LOG.info(("thumbprint renewd key is:\n" + thumbprint_renew_private_key)) + LOG.info(("thumbprint renewed cert is:\n#{thumbprint_renew_cert.cert}")) + LOG.info(("thumbprint renewed key is:\n#{thumbprint_renew_private_key}")) assert (thumbprint_renew_cert.cert != nil ) thumbprint_renew_certificate_object = OpenSSL::X509::Certificate.new(thumbprint_renew_cert.cert) - assert !thumbprint_renew_certificate_object.check_private_key(renew_key_object), "Renewed thumbprint cert signed by same key" + assert !thumbprint_renew_certificate_object.check_private_key(renew_key_object), 'Renewed thumbprint cert signed by same key' thumbprint_renew_key_object = OpenSSL::PKey::RSA.new(thumbprint_renew_private_key) - assert thumbprint_renew_certificate_object.check_private_key(thumbprint_renew_key_object), "Renewed cert signed by the wrong key" - assert (renew_certificate_object.serial != thumbprint_renew_certificate_object.serial), "Original cert sn and renew sn are equal" - assert (thumbprint_renew_certificate_object.subject.to_a.select{|name, _, _| name == 'CN' }.first[1] == renew_certificate_object.subject.to_a.select{|name, _, _| name == 'CN' }.first[1]), "thunbprin certificate CN si different from original" + assert thumbprint_renew_certificate_object.check_private_key(thumbprint_renew_key_object), 'Renewed cert signed by the wrong key' + assert (renew_certificate_object.serial != thumbprint_renew_certificate_object.serial), 'Original cert sn and renew sn are equal' + assert (thumbprint_renew_certificate_object.subject.to_a.select { |name, _, _| name == 'CN' }.first[1] == + renew_certificate_object.subject.to_a.select { |name, _, _| name == 'CN' }.first[1]), 'thumbprint certificate CN is different from original' end @@ -106,18 +109,22 @@ def test_request_cloud end def test_request_fake - request_and_renew(Vcert::Connection.new(fake: true), "fake") + request_and_renew(Vcert::Connection.new(fake: true), 'fake') + end + + def test_request_token + request_and_renew(token_connection, TPPZONE) end def test_zone_configuration_tpp conn = tpp_connection zone = conn.zone_configuration TPPZONE - assert_equal(zone.country.value, "US") - assert_equal(zone.province.value, "Utah") - assert_equal(zone.locality.value, "Salt Lake") - assert_equal(zone.organization.value, "Venafi Inc.") - assert_equal(zone.organizational_unit.value, ["Integrations"]) - assert_equal(zone.key_type.value.type, "rsa") + assert_equal(zone.country.value, 'US') + assert_equal(zone.province.value, 'Utah') + assert_equal(zone.locality.value, 'Salt Lake') + assert_equal(zone.organization.value, 'Venafi Inc.') + assert_equal(zone.organizational_unit.value, ['Integrations']) + assert_equal(zone.key_type.value.type, 'rsa') assert_equal(zone.key_type.value.option, 2048) end @@ -125,44 +132,72 @@ def test_read_policy_tpp conn = tpp_connection policy = conn.policy TPPZONE - assert_equal(policy.instance_variable_get("@policy_id"), '\\VED\\Policy\\' + TPPZONE) - assert_equal(policy.instance_variable_get("@name"), TPPZONE) - assert_equal(policy.instance_variable_get("@subject_cn_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_c_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_st_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_l_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_o_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_ou_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@san_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@key_types").length, 7) + assert_equal(policy.instance_variable_get('@policy_id'), '\\VED\\Policy\\' + TPPZONE) + assert_equal(policy.instance_variable_get('@name'), TPPZONE) + assert_equal(policy.instance_variable_get('@subject_cn_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_c_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_st_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_l_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_o_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_ou_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@san_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@key_types').length, 7) end def test_read_policy_cloud conn = cloud_connection policy = conn.policy CLOUDZONE - assert_equal(policy.instance_variable_get("@subject_cn_regexes"), [".*.example.com", ".*.example.org", ".*.example.net", ".*.invalid", ".*.local", ".*.localhost", ".*.test", ".*.vfidev.com"]) - assert_equal(policy.instance_variable_get("@subject_c_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_st_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_l_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_o_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@subject_ou_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@san_regexes"), [".*"]) - assert_equal(policy.instance_variable_get("@key_types").length, 1) + assert_equal(policy.instance_variable_get('@subject_cn_regexes'), ['.*.example.com', '.*.example.org', '.*.example.net', '.*.invalid', '.*.local', '.*.localhost', '.*.test', '.*.vfidev.com']) + assert_equal(policy.instance_variable_get('@subject_c_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_st_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_l_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_o_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_ou_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@san_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@key_types').length, 1) end def test_zone_configuration_cloud conn = cloud_connection zone = conn.zone_configuration CLOUDZONE - assert_equal(zone.country.value, "") - assert_equal(zone.province.value, "") - assert_equal(zone.locality.value, "") - assert_equal(zone.organization.value, "") - assert_equal(zone.organizational_unit.value, "") - assert_equal(zone.key_type.value.type, "rsa") + assert_equal(zone.country.value, '') + assert_equal(zone.province.value, '') + assert_equal(zone.locality.value, '') + assert_equal(zone.organization.value, '') + assert_equal(zone.organizational_unit.value, '') + assert_equal(zone.key_type.value.type, 'rsa') + assert_equal(zone.key_type.value.option, 2048) + end + + def test_zone_configuration_token + conn = token_connection + zone = conn.zone_configuration TPPZONE + assert_equal(zone.country.value, 'US') + assert_equal(zone.province.value, 'Utah') + assert_equal(zone.locality.value, 'Salt Lake') + assert_equal(zone.organization.value, 'Venafi Inc.') + assert_equal(zone.organizational_unit.value, ['Integrations']) + assert_equal(zone.key_type.value.type, 'rsa') assert_equal(zone.key_type.value.option, 2048) end + def test_read_policy_token + conn = token_connection + + policy = conn.policy TPPZONE + assert_equal(policy.instance_variable_get('@policy_id'), '\\VED\\Policy\\' + TPPZONE) + assert_equal(policy.instance_variable_get('@name'), TPPZONE) + assert_equal(policy.instance_variable_get('@subject_cn_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_c_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_st_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_l_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_o_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@subject_ou_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@san_regexes'), ['.*']) + assert_equal(policy.instance_variable_get('@key_types').length, 7) + end + end def tpp_connection @@ -173,6 +208,10 @@ def cloud_connection Vcert::Connection.new(url: CLOUDURL, cloud_token: CLOUDAPIKEY) end +def token_connection + Vcert::VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE +end + class VcertLocalTest < Minitest::Test def test_generate_csr req = Vcert::Request.new @@ -180,119 +219,154 @@ def test_generate_csr req.csr end req = Vcert::Request.new common_name: random_domain - assert(req.csr.index("-----BEGIN CERTIFICATE REQUEST-----") == 0) + assert(req.csr.index('-----BEGIN CERTIFICATE REQUEST-----').zero?) req = Vcert::Request.new common_name: random_domain, csr: CSR_TEST assert_equal(req.csr, CSR_TEST) - req = Vcert::Request.new common_name: random_domain, organization: "Venafi", organizational_unit: "Devops", country: "US", locality: "Salt Lake", province: "Utah" + req = Vcert::Request.new common_name: random_domain, organization: 'Venafi', organizational_unit: 'Devops', country: 'US', locality: 'Salt Lake', province: 'Utah' temp = req.csr assert_equal(temp, req.csr) - req = Vcert::Request.new common_name: random_domain, key_type: Vcert::KeyType.new("rsa", 4096) + req = Vcert::Request.new common_name: random_domain, key_type: Vcert::KeyType.new('rsa', 4096) csr = OpenSSL::X509::Request.new req.csr assert_equal(csr.public_key.n.num_bytes * 8, 4096) - req = Vcert::Request.new common_name: random_domain, key_type: Vcert::KeyType.new("ecdsa", "prime256v1") + req = Vcert::Request.new common_name: random_domain, key_type: Vcert::KeyType.new('ecdsa', 'prime256v1') csr = OpenSSL::X509::Request.new req.csr assert_instance_of(OpenSSL::PKey::EC, csr.public_key) end def test_match_regexp - p = Vcert::Policy.new policy_id:nil, name:nil, system_generated:nil, creation_date:nil, subject_cn_regexes:nil, subject_o_regexes:nil, - subject_ou_regexes:nil, subject_st_regexes:nil, subject_l_regexes:nil, subject_c_regexes:nil, san_regexes:nil, - key_types:nil - assert(p.send(:match_regexps?, "test", ["test", "ololo"])) - assert(!p.send(:match_regexps?, "test", ["ololo"])) - assert(!p.send(:match_regexps?, "test", [])) - assert(p.send(:match_regexps?, "test", [".*"])) - assert(!p.send(:match_regexps?, "testtest", ["^test$"])) - assert(!p.send(:match_regexps?, "", ["test"])) - assert(p.send(:match_regexps?, "", ["test", ".*"])) + p = Vcert::Policy.new policy_id: nil, name:nil, system_generated:nil, creation_date:nil, subject_cn_regexes:nil, subject_o_regexes:nil, + subject_ou_regexes: nil, subject_st_regexes:nil, subject_l_regexes:nil, subject_c_regexes:nil, san_regexes:nil, + key_types: nil + assert(p.send(:match_regexps?, 'test', ['test', 'ololo'])) + assert(!p.send(:match_regexps?, 'test', ['ololo'])) + assert(!p.send(:match_regexps?, 'test', [])) + assert(p.send(:match_regexps?, 'test', ['.*'])) + assert(!p.send(:match_regexps?, 'testtest', ['^test$'])) + assert(!p.send(:match_regexps?, '', ['test'])) + assert(p.send(:match_regexps?, '', ['test', '.*'])) end def test_key_pairs - p = Vcert::Policy.new policy_id:nil, name:nil, system_generated:nil, creation_date:nil, subject_cn_regexes:nil, subject_o_regexes:nil, - subject_ou_regexes:nil, subject_st_regexes:nil, subject_l_regexes:nil, subject_c_regexes:nil, san_regexes:nil, - key_types:nil - assert(!p.send(:is_key_type_is_valid?, Vcert::KeyType.new("rsa", 2048), [])) - assert(p.send(:is_key_type_is_valid?, Vcert::KeyType.new("rsa", 2048), [Vcert::KeyType.new("ec", "prime256v1"), Vcert::KeyType.new("rsa", 2048)])) - assert(p.send(:is_key_type_is_valid?, Vcert::KeyType.new("rsa", 2048), [Vcert::KeyType.new("rsa", 2048)])) + p = Vcert::Policy.new policy_id: nil, name:nil, system_generated:nil, creation_date:nil, subject_cn_regexes:nil, subject_o_regexes:nil, + subject_ou_regexes: nil, subject_st_regexes:nil, subject_l_regexes:nil, subject_c_regexes:nil, san_regexes:nil, + key_types: nil + assert(!p.send(:is_key_type_is_valid?, Vcert::KeyType.new('rsa', 2048), [])) + assert(p.send(:is_key_type_is_valid?, Vcert::KeyType.new('rsa', 2048), [Vcert::KeyType.new('ec', 'prime256v1'), Vcert::KeyType.new('rsa', 2048)])) + assert(p.send(:is_key_type_is_valid?, Vcert::KeyType.new('rsa', 2048), [Vcert::KeyType.new('rsa', 2048)])) end def test_update_from_zone_config - r = Vcert::Request.new common_name: "test.example.com", country: "US", locality: "New York" + r = Vcert::Request.new common_name: 'test.example.com', country: 'US', locality: 'New York' f = Vcert::CertField - z = Vcert::ZoneConfiguration.new country: f.new("UK"), - province: f.new("Utah"), - locality: f.new("Salt Lake", locked: true), - organization: f.new("Venafi", locked: true), - organizational_unit: f.new(["Integsation", "Devops"]), - key_type: f.new(Vcert::KeyType.new("ec", "prime256v1")) + z = Vcert::ZoneConfiguration.new country: f.new('UK'), + province: f.new('Utah'), + locality: f.new('Salt Lake', locked: true), + organization: f.new('Venafi', locked: true), + organizational_unit: f.new(['Integsation', 'Devops']), + key_type: f.new(Vcert::KeyType.new('ec', 'prime256v1')) r.update_from_zone_config(z) - assert_equal(r.country, "US") - assert_equal(r.province, "Utah") - assert_equal(r.locality, "Salt Lake") - assert_equal(r.organization, "Venafi") - assert_equal(r.organizational_unit, ["Integsation", "Devops"]) - assert_equal(r.key_type.type, "ecdsa") - assert_equal(r.key_type.option, "prime256v1") + assert_equal(r.country, 'US') + assert_equal(r.province, 'Utah') + assert_equal(r.locality, 'Salt Lake') + assert_equal(r.organization, 'Venafi') + assert_equal(r.organizational_unit, ['Integsation', 'Devops']) + assert_equal(r.key_type.type, 'ecdsa') + assert_equal(r.key_type.option, 'prime256v1') end def test_check_with_policies - r = Vcert::Request.new common_name: "test.example.com" + r = Vcert::Request.new common_name: 'test.example.com' p = new_policy_test_wrapper assert_nil(p.check_request(r)) - p = new_policy_test_wrapper(subject_cn_regexes: ["test.venafi.com"]) + p = new_policy_test_wrapper(subject_cn_regexes: ['test.venafi.com']) assert_raises Vcert::ValidationError do p.check_request(r) end - p = new_policy_test_wrapper(subject_cn_regexes: ["test.venafi.com"], key_types: [Vcert::KeyType.new("rsa", 2048), Vcert::KeyType.new("ecdsa", "secp521r1")]) - r = Vcert::Request.new common_name: "test.venafi.com", key_type: Vcert::KeyType.new("ecdsa", "prime256v1") + p = new_policy_test_wrapper(subject_cn_regexes: ['test.venafi.com'], key_types: [Vcert::KeyType.new('rsa', 2048), Vcert::KeyType.new('ecdsa', 'secp521r1')]) + r = Vcert::Request.new common_name: 'test.venafi.com', key_type: Vcert::KeyType.new('ecdsa', 'prime256v1') assert_raises Vcert::ValidationError do p.check_request(r) end #todo: add more tests - p = new_policy_test_wrapper(subject_cn_regexes: ["test.example.com"], key_types: [Vcert::KeyType.new("rsa", 2048), Vcert::KeyType.new("ecdsa", "secp521r1")]) + p = new_policy_test_wrapper(subject_cn_regexes: ['test.example.com'], key_types: [Vcert::KeyType.new('rsa', 2048), Vcert::KeyType.new('ecdsa', 'secp521r1')]) r = Vcert::Request.new csr: CSR_TEST p.check_request r - p = new_policy_test_wrapper(subject_cn_regexes: ["test.venafi.com"], key_types: [Vcert::KeyType.new("rsa", 2048), Vcert::KeyType.new("ecdsa", "secp521r1")]) + p = new_policy_test_wrapper(subject_cn_regexes: ['test.venafi.com'], key_types: [Vcert::KeyType.new('rsa', 2048), Vcert::KeyType.new('ecdsa', 'secp521r1')]) assert_raises Vcert::ValidationError do p.check_request(r) end - p = new_policy_test_wrapper(subject_cn_regexes: ["test.example.com"], key_types: [Vcert::KeyType.new("ecdsa", "secp521r1")]) + p = new_policy_test_wrapper(subject_cn_regexes: ['test.example.com'], key_types: [Vcert::KeyType.new('ecdsa', 'secp521r1')]) assert_raises Vcert::ValidationError do p.check_request(r) end end def test_zone_configuation_parser_tpp - conn = tpp_connection().instance_variable_get("@conn") + conn = tpp_connection.instance_variable_get('@conn') data = JSON.parse '{"Error":null,"Policy":{"CertificateAuthority":{"Locked":false,"Value":"\\VED\\Policy\\devops\\msca_template"},"CsrGeneration":{"Locked":false,"Value":"UserProvided"},"KeyGeneration":{"Locked":false,"Value":"Central"},"KeyPair":{"KeyAlgorithm":{"Locked":false,"Value":"RSA"},"KeySize":{"Locked":false,"Value":2048}},"ManagementType":{"Locked":false,"Value":"Enrollment"},"PrivateKeyReuseAllowed":true,"SubjAltNameDnsAllowed":true,"SubjAltNameEmailAllowed":true,"SubjAltNameIpAllowed":true,"SubjAltNameUpnAllowed":true,"SubjAltNameUriAllowed":true,"Subject":{"City":{"Locked":false,"Value":"Salt Lake"},"Country":{"Locked":false,"Value":"US"},"Organization":{"Locked":false,"Value":"Venafi Inc."},"OrganizationalUnit":{"Locked":false,"Values":["Integrations"]},"State":{"Locked":false,"Value":"Utah"}},"UniqueSubjectEnforced":false,"WhitelistedDomains":[],"WildcardsAllowed":true}}' zone = conn.send(:parse_zone_configuration, data) - assert_equal(zone.key_type.value.type, "rsa") + assert_equal(zone.key_type.value.type, 'rsa') assert_equal(zone.key_type.value.option, 2048) # todo: add more tests end def test_policy_parser_tpp - conn = tpp_connection().instance_variable_get("@conn") + conn = tpp_connection().instance_variable_get('@conn') data = JSON.parse '{"Error":null,"Policy":{"CertificateAuthority":{"Locked":false,"Value":"\\VED\\Policy\\devops\\msca_template"},"CsrGeneration":{"Locked":false,"Value":"UserProvided"},"KeyGeneration":{"Locked":false,"Value":"Central"},"KeyPair":{"KeyAlgorithm":{"Locked":false,"Value":"RSA"},"KeySize":{"Locked":false,"Value":2048}},"ManagementType":{"Locked":false,"Value":"Enrollment"},"PrivateKeyReuseAllowed":true,"SubjAltNameDnsAllowed":true,"SubjAltNameEmailAllowed":true,"SubjAltNameIpAllowed":true,"SubjAltNameUpnAllowed":true,"SubjAltNameUriAllowed":true,"Subject":{"City":{"Locked":false,"Value":"Salt Lake"},"Country":{"Locked":false,"Value":"US"},"Organization":{"Locked":false,"Value":"Venafi Inc."},"OrganizationalUnit":{"Locked":false,"Values":["Integrations"]},"State":{"Locked":false,"Value":"Utah"}},"UniqueSubjectEnforced":false,"WhitelistedDomains":[],"WildcardsAllowed":true}}' - policy = conn.send(:parse_policy_response, data, "TestZone") - assert_equal(policy.instance_variable_get("@subject_cn_regexes"), [".*"]) + policy = conn.send(:parse_policy_response, data, 'TestZone') + assert_equal(policy.instance_variable_get('@subject_cn_regexes'), ['.*']) #todo add more tests end end -def new_policy_test_wrapper(policy_id: nil, name: "", system_generated: false, creation_date: nil, - subject_cn_regexes:[".*"], subject_o_regexes: [".*"], subject_ou_regexes:[".*"], - subject_st_regexes:[".*"], subject_l_regexes:[".*"], subject_c_regexes:[".*"], - san_regexes:[".*"], key_types: nil) +def new_policy_test_wrapper(policy_id: nil, name: '', system_generated: false, creation_date: nil, + subject_cn_regexes:['.*'], subject_o_regexes: ['.*'], subject_ou_regexes:['.*'], + subject_st_regexes:['.*'], subject_l_regexes:['.*'], subject_c_regexes:['.*'], + san_regexes:['.*'], key_types: nil) if key_types == nil - key_types = [1024, 2048, 4096, 8192].map {|s| Vcert::KeyType.new("rsa", s) } + Vcert::SUPPORTED_CURVES.map {|c| Vcert::KeyType.new("ecdsa", c) } + key_types = [1024, 2048, 4096, 8192].map {|s| Vcert::KeyType.new('rsa', s) } + Vcert::SUPPORTED_CURVES.map {|c| Vcert::KeyType.new('ecdsa', c) } end Vcert::Policy.new(policy_id: policy_id, name: name, system_generated: system_generated, creation_date: creation_date, subject_cn_regexes: subject_cn_regexes, subject_o_regexes: subject_o_regexes, subject_ou_regexes: subject_ou_regexes, subject_st_regexes: subject_st_regexes, subject_l_regexes: subject_l_regexes, subject_c_regexes: subject_c_regexes, san_regexes: san_regexes, key_types: key_types) +end + +class VcertTokenTest < Minitest::Test + def test_access_token + conn = Vcert::VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE + token_info = conn.get_access_token + assert !token_info.access_token.nil?, 'Expected access token but is nil.' + assert !token_info.refresh_token.nil?, 'Expected refresh token but is nil.' + assert !token_info.expires.nil?, 'Expected expiration date, but is nil' + end + + def test_refresh_token + conn = Vcert::VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE + token_info = conn.get_access_token + old_access_token = token_info.access_token + LOG.info(" Old access token is: [#{old_access_token}]") + old_refresh_token = token_info.refresh_token + LOG.info(" Old refresh token is: [#{old_refresh_token}]") + + token_info = conn.refresh_access_token + new_access_token = token_info.access_token + LOG.info(" New access token is: [#{new_access_token}]") + new_refresh_token = token_info.refresh_token + LOG.info(" New refresh token is: [#{new_refresh_token}]") + + assert old_access_token != new_access_token, "Expected new access token, but got:\nOld= [#{old_access_token}],\nNew= [#{new_access_token}]" + assert old_refresh_token != new_refresh_token, "Expected new refresh token, but got:\nOld= [#{old_refresh_token}]\nNew= [#{new_refresh_token}]" + end + + def test_revoke_token + conn = Vcert::VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE + conn.get_access_token + conn.revoke_access_token + assert_raises(Vcert::ServerUnexpectedBehaviorError) { conn.zone_configuration TPPZONE } + end end \ No newline at end of file diff --git a/vcert.gemspec b/vcert.gemspec index c13a478..5dd4555 100644 --- a/vcert.gemspec +++ b/vcert.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |s| s.description = "Ruby client for Venafi Cloud and Trust Protection Platform" s.authors = ["Denis Subbotin", "Alexander Rykalin"] s.email = 'opensource@venafi.com' - s.files = ["lib/vcert.rb", "lib/cloud/cloud.rb", "lib/tpp/tpp.rb", "lib/objects/objects.rb", "lib/fake/fake.rb", "lib/utils/utils.rb"] + s.files = ["lib/vcert.rb", "lib/cloud/cloud.rb", "lib/tpp/tpp.rb", "lib/tpp/tpp_token.rb", "lib/objects/objects.rb", "lib/fake/fake.rb", "lib/utils/utils.rb"] s.homepage = 'https://rubygems.org/gems/vcert' s.license = 'Apache-2.0'