Merge pull request #6018 from inspec/ss/add-x509_private_key-resource

CFINSPEC-84: Add `x509_private_key` resource
This commit is contained in:
Vasundhara Jagdale 2022-05-17 06:00:03 +00:00 committed by GitHub
commit a0fba6d46d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 301 additions and 0 deletions

View file

@ -0,0 +1,113 @@
+++
title = "x509_private_key resource"
draft = false
gh_repo = "inspec"
platform = "unix"
[menu]
[menu.inspec]
title = "x509_private_key"
identifier = "inspec/resources/os/x509_private_key.md x509_private_key resource"
parent = "inspec/resources/os"
+++
Use the `x509_private_key` Chef InSpec audit resource to test the x509 private key.
## Availability
### Installation
The Chef InSpec distributes this resource.
## Syntax
An `x509_private_key` Chef InSpec audit resource allows you to test the x509 private key.
```ruby
describe x509_private_key("/home/x509_private_key.pem", "key_password") do
it { should be_valid }
it { should be_encrypted }
it { should have_matching_certificate("/home/x509_certificate.crt") }
end
```
> where
>
> - `"/home/x509_private_key.pem"` is the path of the private key.
> - `"key_password"` is the password of the private key. This is optional for private keys without password.
> - `be_valid`, `be_encrypted`, and `have_matching_certificate` are matchers of this resource
> - `"/home/x509_certificate.crt"` is a x509 certificate generated using the specified private key.
## Matchers
For a full list of available matchers, please visit the [matchers page](https://docs.chef.io/inspec/matchers/).
The specific matchers of this resource are: `be_valid`, `be_encrypted`, and `have_matching_certificate`.
### be_valid
The `be_valid` matcher tests if the specified private key is valid.
```ruby
it { should be_valid }
```
### be_encrypted
The `be_encrypted` matcher tests if the specified private key is encrypted.
```ruby
it { should be_encrypted }
```
### have_matching_certificate
The `have_matching_certificate` matcher tests if the x509 private key has a matching certificate.
```ruby
it { should have_matching_certificate("/home/x509_certificate.crt") }
```
## Examples
The following examples show how to use this Chef InSpec audit resource.
### Checks if the x509 private key is valid
`be_valid` checks if the x509 private key is valid.
```ruby
describe x509_private_key("/home/x509_private_key.pem", "key_password") do
it { should be_valid }
end
```
### Checks if the x509 private key is valid without a password
`be_valid` checks if the x509 private key is valid.
```ruby
describe x509_private_key("/home/x509_private_key.pem") do
it { should be_valid }
end
```
### Checks if the x509 private key is encrypted
`be_encrypted` checks if the x509 private key is encrypted.
```ruby
describe x509_private_key("/home/x509_private_key.pem", "key_password") do
it { should be_encrypted }
end
```
### Checks if the x509 private key has a matching certificate
`be_valid` checks if the x509 private key has a matching certificate.
```ruby
describe x509_private_key("/home/x509_private_key.pem", "key_password") do
it { should have_matching_certificate("/home/x509_certificate.crt") }
end
```

View file

@ -0,0 +1,93 @@
require "inspec/resources/file"
module Inspec::Resources
class X509PrivateKey < Inspec.resource(1)
# Resource internal name.
name "x509_private_key"
# Restrict to only run on the below platforms (if none were given,
# all OS's and cloud API's supported)
supports platform: "unix"
supports platform: "windows"
desc "Use the x509_private_key InSpec audit resource to test the x509 private key"
example <<~EXAMPLE
# With passphrase
describe x509_private_key("/home/openssl_activity/alice_private.pem", "password@123") do
it { should be_valid }
it { should be_encrypted }
it { should have_matching_certificate("/home/openssl_activity/alice_certificate.crt") }
end
# Without passphrase
describe x509_private_key("/home/openssl_activity/bob_private.pem") do
it { should be_valid }
it { should_not be_encrypted }
it { should have_matching_certificate("/home/openssl_activity/bob_certificate.crt") }
end
EXAMPLE
# Resource initialization.
attr_reader :secret_key_path, :passphrase, :openssl_utility
def initialize(secret_key_path, passphrase = nil)
@openssl_utility = check_openssl_or_error
@secret_key_path = secret_key_path
@passphrase = passphrase
end
# Resource appearance in test reports.
def to_s
"x509_private_key"
end
# Matcher to check if the given key is valid.
def valid?
# Below is the command to check if the key is valid.
openssl_key_validity_cmd = "#{openssl_utility} rsa -in #{secret_key_path} -check -noout"
# Additionally, if key is password protected, passphrase needs to be given with -passin argument
openssl_key_validity_cmd.concat(" -passin pass:#{passphrase}") if passphrase
openssl_key_validity = inspec.command(openssl_key_validity_cmd)
openssl_key_validity.exit_status.to_i == 0
end
# Matcher to check if the given key is encrypted.
def encrypted?
raise Inspec::Exceptions::ResourceFailed, "The given secret key #{secret_key_path} does not exist." unless inspec.file(secret_key_path).exist?
# All encrypted keys have the header of Proc-Type: 4,ENCRYPTED
key_file = inspec.file(secret_key_path)
key_file.content =~ /Proc-Type: 4,ENCRYPTED/
end
# Matcher to verify if the private key maatches the certificate
def has_matching_certificate?(cert_file_or_path)
cert_hash_cmd = "openssl x509 -noout -modulus -in #{cert_file_or_path} | openssl md5"
cert_hash = inspec.command(cert_hash_cmd)
raise Inspec::Exceptions::ResourceFailed, "Executing #{cert_hash_cmd} failed: #{cert_hash.stderr}" if cert_hash.exit_status.to_i != 0
key_hash_cmd = "openssl rsa -noout -modulus -in #{secret_key_path}"
passphrase ? key_hash_cmd.concat(" -passin pass:#{passphrase} | openssl md5") : key_hash_cmd.concat(" | openssl md5")
key_hash = inspec.command(key_hash_cmd)
raise Inspec::Exceptions::ResourceFailed, "Executing #{key_hash_cmd} failed: #{key_hash.stderr}" if key_hash.exit_status.to_i != 0
cert_hash.stdout == key_hash.stdout
end
private
# This resource requires openssl to be available on the system
def check_openssl_or_error
%w{/usr/sbin/openssl /usr/bin/openssl /sbin/openssl /bin/openssl openssl}.each do |cmd|
return cmd if inspec.command(cmd).exist?
end
raise Inspec::Exceptions::ResourceFailed, "Could not find `openssl` on your system."
end
end
end

View file

@ -0,0 +1 @@
2c4097e9fb7a3df2851f110d6d69de66

View file

@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,F4D341E5BD7D12654CD4F1E3F9E10AA3
wOispCvsWL+/exVJ1W97DoM9jFDUKadlp7nnmRXQqjYYNkrHdPcZkKnqdkwGlEoK
j0DPgxYOvP97arIuLwpIjrsujBI7jBZdsir6yxOQIypTS+uZnX2sJ6rgcJpRLTbj
7effshcVY3Q5v8nIxAADKfO/vJcMQZXMfDkwLCNEFFanI8hR3ZqAIwEPhaQDOLJr
jPo5emLb7pP30pqbxhIyWcI1B8MrvCWcz9D58TGtwJG2SCVYtTPhYGI8BI38ONHT
VRslMHzSS1I+XJcQ3Y7qkont4P9GFXQSGMaQfORMSYbT8Jik5URsXG6kMMTXWExm
QbZT9TyvLWUJ+CrmrZ69T8YLpizOINLGtfVUP4CVBdxNw3E/WOa6uTWg0OCZhxXX
JA1ifAmOCMDx5rdqsKcWpaUt/ESZh1VUZuxwzHpdr5FBz3eMgaTX9vyiU5LxW5r7
CYgmwJ9RYh7zzzAto0pL3DFnjnDGQvkzvDCpBjY+ag1PiTf0BJ8yi5mzMWS5vpz6
4mefKFHjEOYVLRNNbN12rOfmaaSzKBAGECoVQdDSQ91W5eGzX2rlhS8S1BWCGZNr
f35QlSDwJm0jW2/3EJtiEBazyVMKi56nSZ8zItzHkxzA1Yr+2xz98JsccoZAlSWg
+VRP3HJs46MvpLECpRAbPIJ9DtQhv1sZvrpw6U6EEXvAs0atJlfSdsHp2BJs4F2G
jhT5XIuUBT/cK1IAnMZzpRgSeQ80ctvU1z+iybKvh/OBxtkTIezx1pz/g/KyU1//
pX7nMe1Pi2blZofYCM+C4ATJxrBdnjrUiLGoxCJ0De/JweAcXiS35N1qcRf8Ldsz
-----END RSA PRIVATE KEY-----

15
test/fixtures/files/x509-secret-key vendored Normal file
View file

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDwz32N81R2bcoPbqgaegDEAvqCN9itxllRTT1351jq/50q8nUi
G7OH2k/bX6vNCDsirfVlCmVjkYaE+DGszWrR5OTGWVPsdfQERT0SISKfSkAUli87
oq6eDnTJYvSOfDjM3VDVX/r5EaBhYyNljqT7U1liMh0oEN8lBoS4WR7kpQIDAQAB
AoGBAMz3buCx6oRa8o+sGj1e9uPfvSW1LyLKHyAKZHV4XsC3DSmO8ZA0j9zkPvB0
QCDmDp93/dUgwie7qAnpzy5MMcwLFKszry7k8Bdaku4p9213y5waUw/qM+NORi7m
W1fPcnknYI0sZyBkww4KSIHKFx8WCMTWqEVQCiOpevWSyJIBAkEA/DPXQVCP2I6a
W1UOo9+S93b5yw+dRI24xP69tViM9oSW1tygkk/m5mkHseGF2OMDjTbhJzxoWCDJ
MNaFilqawQJBAPRvvLX+Q9ddFAbO8YqCLACTwllQFvM+p6bpIWt1TJATD0+ET+md
nIu0PV7NtZYykL0vsumSqrOGA0NX592d9uUCQQCSHyshYZ7mNsFCF4by9W9+R4W0
3CgfdwlNwdaCgnqxveJzPMMf4oGCj+nwax0Uq3r8T4amJ/7AyMYosLIQlixBAkEA
rugRgayqgL2SqfiEr9bLg7I3XE9JzM6linLBPjJWEbYBu6VyxTjJntHfJCpQaGRQ
W395J1eSnBkl1pddS6BP3QJBALXGPmw0os1meuuEngJSlFhcev93Inqn/5rKJUbP
F6+pZq8RsW9bSKYwq2Uk6OwTizxCYLp1KxEzsnPUGE4AfeI=
-----END RSA PRIVATE KEY-----

View file

@ -184,6 +184,9 @@ class MockLoader
"myjson.json" => mockfile.call("node.json"),
"myyaml.yml" => mockfile.call("kitchen.yml"),
"myinvalid.file" => mockfile.call("default.xml"),
# x509_secret_key
"/home/openssl_activity/bob_private.pem" => mockfile.call("x509-secret-key"),
"/home/openssl_activity/alice_private.pem" => mockfile.call("x509-encrypted-secret-key"),
}
mock.files = mock_files
@ -408,6 +411,16 @@ class MockLoader
"/usr/sbin/auditctl -s | grep pid" => cmd.call("auditctl-s-pid"),
"/usr/sbin/auditctl -l" => cmd.call("auditctl-l"),
%{sh -c 'type "/usr/sbin/auditctl"'} => empty.call,
# x509_private_key
%{sh -c 'type "openssl"'} => empty.call,
%{type "openssl"} => empty.call,
"openssl rsa -in /home/openssl_activity/bob_private.pem -check -noout" => empty.call,
"openssl rsa -in /home/openssl_activity/alice_private.pem -check -noout -passin pass:password@123" => empty.call,
"openssl x509 -noout -modulus -in /home/openssl_activity/bob_certificate.crt | openssl md5" => cmd.call("x509-certificate-modulus"),
"openssl rsa -noout -modulus -in /home/openssl_activity/bob_private.pem | openssl md5" => cmd.call("x509-certificate-modulus"),
"openssl x509 -noout -modulus -in /home/openssl_activity/alice_certificate.crt | openssl md5" => cmd.call("x509-certificate-modulus"),
"openssl rsa -noout -modulus -in /home/openssl_activity/alice_private.pem -passin pass:password@123 | openssl md5" => cmd.call("x509-certificate-modulus"),
# apache_conf
"sh -c 'find /etc/apache2/ports.conf -type f -maxdepth 1'" => cmd.call("find-apache2-ports-conf"),
"sh -c 'find /etc/httpd/conf.d/*.conf -type f -maxdepth 1'" => cmd.call("find-httpd-ssl-conf"),

View file

@ -0,0 +1,48 @@
require "inspec/globals"
require "#{Inspec.src_root}/test/helper"
require_relative "../../../lib/inspec/resources/x509_private_key"
describe Inspec::Resources::X509PrivateKey do
# linux
it "checks x509 secret key with no passphrase on linux" do
resource = MockLoader.new("ubuntu".to_sym).load_resource("x509_private_key", "/home/openssl_activity/bob_private.pem")
_(resource.valid?).must_equal true
_(resource.encrypted?).must_be_nil
_(resource.has_matching_certificate?("/home/openssl_activity/bob_certificate.crt")).must_equal true
end
# linux - with password
it "checks x509 secret key with passphrase on linux" do
resource = MockLoader.new("ubuntu".to_sym).load_resource("x509_private_key", "/home/openssl_activity/alice_private.pem", "password@123")
_(resource.valid?).must_equal true
_(resource.encrypted?).wont_be_nil
_(resource.has_matching_certificate?("/home/openssl_activity/alice_certificate.crt")).must_equal true
end
# darwin
it "checks x509 secret key with no passphrase on darwin" do
resource = MockLoader.new(:macos10_10).load_resource("x509_private_key", "/home/openssl_activity/bob_private.pem")
_(resource.valid?).must_equal true
_(resource.encrypted?).must_be_nil
_(resource.has_matching_certificate?("/home/openssl_activity/bob_certificate.crt")).must_equal true
end
# freebsd
it "checks x509 secret key with no passphrase on freebsd" do
resource = MockLoader.new(:freebsd10).load_resource("x509_private_key", "/home/openssl_activity/bob_private.pem")
_(resource.valid?).must_equal true
_(resource.encrypted?).must_be_nil
_(resource.has_matching_certificate?("/home/openssl_activity/bob_certificate.crt")).must_equal true
end
# linux
it "checks unavailable x509 secret key with no passphrase on linux" do
secret_key_path = "/home/openssl_activity/ghost_private.pem"
resource = MockLoader.new("ubuntu".to_sym).load_resource("x509_private_key", secret_key_path)
_(resource.valid?).must_equal false
ex = _ { resource.encrypted? }.must_raise(Inspec::Exceptions::ResourceFailed)
_(ex.message).must_include "The given secret key #{secret_key_path} does not exist."
ex = _ { resource.has_matching_certificate?("/home/openssl_activity/ghost_cert.crt") }.must_raise(Inspec::Exceptions::ResourceFailed)
_(ex.message).must_include "Executing openssl x509 -noout -modulus -in /home/openssl_activity/ghost_cert.crt | openssl md5 failed:"
end
end