mirror of
https://github.com/inspec/inspec
synced 2024-11-26 14:40:26 +00:00
Merge pull request #6018 from inspec/ss/add-x509_private_key-resource
CFINSPEC-84: Add `x509_private_key` resource
This commit is contained in:
commit
a0fba6d46d
7 changed files with 301 additions and 0 deletions
113
docs-chef-io/content/inspec/resources/x509_private_key.md
Normal file
113
docs-chef-io/content/inspec/resources/x509_private_key.md
Normal 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
|
||||
```
|
93
lib/inspec/resources/x509_private_key.rb
Normal file
93
lib/inspec/resources/x509_private_key.rb
Normal 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
|
1
test/fixtures/cmd/x509-certificate-modulus
vendored
Normal file
1
test/fixtures/cmd/x509-certificate-modulus
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
2c4097e9fb7a3df2851f110d6d69de66
|
18
test/fixtures/files/x509-encrypted-secret-key
vendored
Normal file
18
test/fixtures/files/x509-encrypted-secret-key
vendored
Normal 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
15
test/fixtures/files/x509-secret-key
vendored
Normal 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-----
|
|
@ -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"),
|
||||
|
|
48
test/unit/resources/x509_private_key_test.rb
Normal file
48
test/unit/resources/x509_private_key_test.rb
Normal 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
|
Loading…
Reference in a new issue