Ansible: Create a Self-Signed SSL Certificate and Key - ssl

I want to create a self signed certificate to use it with stunnel, in order to securely tunnel my redis traffic between the redis server and client. I'm using this command to generate the certificate and it works fine.
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/stunnel/redis-server.key -out /etc/stunnel/redis-server.crt
Since I'm using Ansible for provisioning, I would like to know how I can convert this into a more Ansible way of doing, using a module. There actually is a module called the openssl_certificate Ansible module and it states "This module allows one to (re)generate OpenSSL certificates.". I tried to use the module to generate the certificate, but I couldn't get it to work.
- name: Generate a Self Signed OpenSSL certificate
openssl_certificate:
path: /etc/stunnel/redis-server.crt
privatekey_path: /etc/stunnel/redis-server.key
csr_path: /etc/stunnel/redis-server.csr
provider: selfsigned
From a look at the documentation, I can't specify the following arguments -x509 -nodes -days 3650 -newkey rsa:2048. Of course, I could also split the key and cert generation, but that still wouldn't allow me to use the Ansible module, correct?
Example given:
openssl genrsa -out /etc/stunnel/key.pem 4096
openssl req -new -x509 -key /etc/stunnel/key.pem -out /etc/stunnel/cert.pem -days 1826
I would like to know the following things:
a) How can I get the same result from the original command, but using an Ansible module?
b) Is there a better way to manage self signed certificates using Ansible?

- openssl_privatekey:
path: /etc/stunnel/redis-server.key
size: 2048
- openssl_csr:
path: /etc/stunnel/redis-server.csr
privatekey_path: /etc/stunnel/redis-server.key
- openssl_certificate:
provider: selfsigned
path: /etc/stunnel/redis-server.crt
privatekey_path: /etc/stunnel/redis-server.key
csr_path:/etc/stunnel/redis-server.csr
"-newkey rsa:2048" is dealt with "size" on privatekey
"-x509" is defaulted
"-nodes" and "-days 3650" I couldn't find

You can achieve this in 2 steps (tested with Ansible 5.7.1):
- name: Ensure private key is present
community.crypto.openssl_privatekey:
path: /etc/stunnel/redis-server.key
size: 2048
mode: 0600
type: RSA
- name: Ensure self-signed cert is present
community.crypto.x509_certificate:
path: /etc/stunnel/redis-server.crt
privatekey_path: /etc/stunnel/redis-server.key
provider: selfsigned
selfsigned_not_after: "+3650d" # this is the default
mode: 0644
If you don't set a passphrase in the private key generation task, it will be password-less by default.
As #nondeterministic mentioned before, you can set the expiration date via selfsigned_not_after.
The mode parameter sets the file permissions of the key and cert files. It's a common best practice to set the permissions whenever possible and it is also checked per default by ansible-lint.
Sources:
https://docs.ansible.com/ansible/latest/collections/community/crypto/openssl_privatekey_module.html
https://docs.ansible.com/ansible/latest/collections/community/crypto/x509_certificate_module.html

Related

Don't Ask question when generate SSL certificate

Sometimes I test an SSL website on my local machine. I was tired to use a self-signed certificate and add them to my KeyChain on Mac (Browser or other OS). Moreover, Chrome always complains about them. Moreover, this approach was a bit different from the one used in production.
I found this article very useful where you create once your own CA root certificate, add it once to your keychain and then you use the CA private key to sign thousands of SSL test certificate for my local websites.
https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/
The tutorial works great but I would like to automate it. For the CA root certificate it was easy, I simply used the option -subj like this:
openssl req -x509 -new -nodes -key /certs/myCA.key -sha256 -days 1825 -subj "/C=$CA_COUNTRY/ST=$CA_STATE/L=$CA_CITY/O=$CA_ORGANIZATION/CN=$CA_COMMON_NAME" -out /certs/myCA2.pem
where the environment variable (CA_COUNTRY, CA_STATE, CA_CITY, CA_ORGANIZATION, CA_COMMON_NAME) are read from an external file.
However, when I tried to replicate the same thing for the website certificate I wasn't able to get the same result. The command is this:
openssl x509 -req -in dev.deliciousbrains.com.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out dev.deliciousbrains.com.crt -days 825 -sha256 -extfile dev.deliciousbrains.com.ext
It seems that the -subj option doesn't work. Is there a way to pass the info above to this command and avoid interactive questions?
The command you show openssl x509 -req -CA/-CAkey ... does not ask any questions except the key password if there is one (which if you followed the instructions at the linked page there is). It is the preceding command to create the CSR openssl req -new that prompts for the subject name, and for that (like the command for creating the CA cert which is also req but with -x509 -- note -x509 is not the same as x509) you can use -subj. The statement on that page that "your answers don’t matter" isn't quite correct; it is true that when you use SubjectAlternativeName in the leaf cert, as that page advises/directs, the value of Subject is ignored for (at least) HTTPS server identification, but it must (still) be different from the name used for the CA to allow certificate validation to work. Standards allow the Subject name in a leaf cert to be empty when SAN is used (and empty is always different from nonempty and a nonempty name is required in the CA cert) but OpenSSL doesn't handle that case.

Page is working but showing "Not Secure" sign on browser (configured with Traefik v2 using self-signed cert)

I apologize if this is a silly rookie question, I'm not really experience in dealing with SSL / https so please help me out.
I have docker swarm setup and using Traefik to handle all the HTTPS services. when I first load the page (take grafana page for example), there is a warning page and I click "Advanced" and "Proceed (accept risk)", then the page display and working just fine, the only problem is the "Not Secure" sign showing on browser.
A few things could be contributing to this:
Self-created CA and self-signed cert: I'm at development stage so I created my own CA and signed the cert using openssl, and use this cert in Traefik dynamic configuration.
Command to generate CA:
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca.pem
Command to generate self-signed cert:
openssl req -newkey rsa:2048 -days 365 -nodes -keyout key.pem -out req.pem
openssl x509 -req -in req.pem -days 365 -CA ca.pem -CAkey ca-key.pem -set_serial 01 -out cert.pem
See attached screenshot for the errors of the certs: "Subject Alternative Name missing" & "This site is missing a valid, trusted certificate (net::ERR_CERT_AUTHORITY_INVALID)."
Chrome Dev Tool Certificate Error
Traefik configuration: Not using Let's Encrypt since I don't have an account, so using my own self-signed cert. I don't think this is the issue because I can see the page is using the cert I provided. But if anyone has similar experience with Traefik v2 maybe can give me some pointer if there is anything I set wrong?
Dynamic configuration file that declares the certs:
tls:
stores:
default:
defaultCertificate:
certFile: configuration/cert.pem
keyFile: configuration/key.pem
Question:
Is missing SAN a really important factor that will causes my page to be not secure? If yes, how can I add SAN while creating cert with openssl?
I understand that 2nd error "ERR_CERT_AUTHORITY_INVALID" means browser doesn't recognize the cert's validity. Does that mean I have to install my CA? Where and how to install it? Is it on docker swarm's manager node (this is where Traefik service and the certs at), or is it on any client's machine that trying to access the page?

How to serve a Vue application over HTTPS for local development

I need to serve a vue application over HTTPS while doing local development.
The application is being served with: npm run serve which runs: vue-cli-service serve
I have tried to create a vue.config.js file and add the following to it:
module.exports = {
devServer: {
port: 8080,
https: true,
}
}
This results in console errors in Chrome v75 such as the following: GET https://192.168.0.71:8080/sockjs-node/info?t=1564339649757 net::ERR_CERT_AUTHORITY_INVALID I'm guessing this is Chrome saying that the certificate being used when setting https to true isn't from a valid CA (maybe it's some sort of self signed thing going on in the background?)
How can I get around this? Is generating certificates via "Let's Encrypt" probably the way to go?
On another note, I have also generated a root CA private key using openssl genrsa -des3 -out rootCA.key 2048 and a self signed certificate using openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem, but I'm not sure how to tell the vue-cli-service to try and use these. However, if self signed certificates result in ERR_CERT_AUTHORITY_INVALID errors in Chrome, then there isn't much point pursuing this route
Go to your network tab in the Chrome console.
Double click on the failing https://192.168.0.71:8080/sockjs-node/info?t=1564339649757 (Opens in new tab)
Accept exemption for the invalid cert
What I ended up doing was creating a shell script with this in it:
echo "Started local certificate setup script."
openssl genrsa -des3 -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 825 -out rootCA.pem
openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config server.csr.cnf
openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 825 -sha256 -extfile v3.ext
echo "Trust the certificate (add it to the system keychain): "
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain rootCA.pem
Basically you create a root CA and have it sign your cert.
Note: the "security add-trusted-cert" step will have to be modified if you aren't on macOS. This step adds it to the macOS keychain.
v3.ext contains:
authorityKeyIdentifier = keyid, issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = #alt_names
[alt_names]
DNS.1 = localhost
server.csr.cnf contains:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[dn]
C=CA
ST=RandomProvince
L=RandomCity
O=RandomOrg
OU=RandomOrgUnit
emailAddress=admin#somedomain.com
CN = localhost
If you're including this in your project, then you'll probably also want to add the following entries to .gitignore:
*.key
*.srl
*.csr
*.pem
*.crt
In my config file (I'm using nuxt.js now) I have the following:
server: {
port: 7000,
host: 'localhost',
timing: false,
https: {
// these files are generated by running the above shell script
key: fs.readFileSync(path.resolve(__dirname, 'server.key')),
cert: fs.readFileSync(path.resolve(__dirname, 'server.crt')),
},
},
Having a script do this is nice so that team members that might not be familiar with this sort of crypto stuff don't have to dig into the details too much and can just start writing code!
Not too sure what your webpack configuration is, but mine has a dev-server.js file inside the build folder. To make https work on the local machine, I had to replace the line const server = app.listen(port) with the following code:
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.cert')
}
const server = https.createServer(options, app).listen(port);
Note that you might need to change the path to your certificates.
Also change const uri = 'http://localhost:' + port to const uri = 'https://localhost:' + port
As long as you have, at least, self signed certificates and keys then all you would have to do is run the command "npm run serve --https"
LEts Encrypt solved it for me. I just generated a certificate for my localhost, added that to gitignore and snap. Error gone. Try this: https://letsencrypt.org/docs/certificates-for-localhost/
You can use mkcert https://github.com/FiloSottile/mkcert
It will create a fake CA certificate for your localhost and you can configure your local server to use it
simply add this into your vue.config.js
module.exports = {
...
devServer: {
https: true
}
}

Ansible X509 certificate missing Subject Alternative Name

I'am using Vagrant and Ansible roles to generate an SSL/TLS certificate but no matter what I try, the generated certificates is missing the Subject Alternative Name:
- name: Create an SSL security key & CSR (Certificate Signing Request)
shell: openssl req -new -newkey rsa:2048 -nodes -keyout /etc/apache2/ssl/{{ item.host }}.key -subj "/subjectAltName=DNS.1={{ item.host }}, DNS.2=www.{{ item.host }}, IP.1=192.168.33.11/C={{params['ssl'].country_name}}/ST={{params['ssl'].state}}/L={{params['ssl'].locality}}/O={{params['ssl'].organization}}/CN={{ item.host }}" -out /etc/apache2/ssl/{{ item.host }}.csr
args:
executable: "/bin/bash"
with_items: "{{params['vhosts']}}"
when: item.ssl is defined and item.ssl
The certificate files gets generated but they Google Chrome always says
Subject Alternative Name Missing
This is the debug of my environment:
$ openssl version
OpenSSL 1.0.2l 25 May 2017
$ openssl x509 -noout -text -in /etc/apache2/ssl/myhost.dev.crt
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
a2:77:35:c7:6a:72:35:22
Signature Algorithm: sha256WithRSAEncryption
Issuer: subjectAltName=DNS.1=myhost.dev, DNS.2=www.myhost.dev, IP.1=192.168.33.11, C=DE, ST=Berlin, L=Berlin, O=Ltd, CN=myhost.dev
Validity
Not Before: Jun 12 15:36:58 2017 GMT
Not After : Jun 10 15:36:58 2027 GMT
Subject: subjectAltName=DNS.1=myhost.dev, DNS.2=www.myhost.dev, IP.1=192.168.33.11, C=DE, ST=Berlin, L=Berlin, O=Ltd, CN=myhost.dev
Your key isn't using X509 extensions. In order to add them to your CSR, you'll need a config file that specifies what extensions to add. The command line interface isn't friendly enough to let you easily specify X509 extensions on the command line.
What you could do is use Bash's process substitution with a command that generates a modified config file on the fly when you invoke openssl to generate your CSR:
openssl req \
-new -newkey rsa:2048 \
-subj "{your existing subject}" \
... \
-x509 \
-reqexts SAN \
-config <(
cat /etc/ssl/openssl.cnf
printf '\n[SAN]\nsubjectAltName=DNS:example.com,DNS:www.example.com'
)
Again, process substitution only works in GNU bash, and will not work if your CI runner's default shell is Bourne Shell, as it sometimes is on Ubuntu-based distros.
This answer was adapted from here.
After some research on the openssl library and understanding how it works, I was doing the mistake of using -X509*: adding -X509 will create a certificate and not a request!
I solved my issue by following this main steps:
Set up a certificate authority: entity that issues digital
certificates.
Create server or user certificate request.
Sign the server certificate request.
Add this keys and certificates to your host.
Add the certificates to the browser.
I wrote a step by step long tutorial on how to achieve this on my blog post.

Apache warns that my self-signed certificate is a CA certificate

As I don't know the openssl command's arguments by heart, I am used to referring to the same SO answer whenever I need to create self-signed certificates (for testing environments). The command looks like:
openssl req -x509 -nodes -newkey rsa:2048 -keyout mysite.key -out mysite.crt -days 365
And it usually works, for instance on my current Ubuntu 15.10. Today I'm on a fresh install of Debian Jessie and it doesn't. Apache warns at startup that:
[ssl:warn] [pid 1040] AH01906: www.mysite.com:443:0 server certificate is a CA certificate (BasicConstraints: CA == TRUE !?)
I looked for a solution to the problem and found an answer in a linux forum stating that the following should be used instead:
openssl genrsa -des3 -passout pass:x -out mysite.pass.key 2048
openssl rsa -passin pass:x -in mysite.pass.key -out mysite.key
openssl req -new -key mysite.key -out mysite.csr
openssl x509 -req -days 365 -in mysite.csr -signkey mysite.key -out mysite.crt
And it's true, this way the Apache warning disappears.
As far as I understand, this creates a passphrase-protected key, then removes the passphrase, then creates a CSR, then generates the certificate with both the CSR and the key.
So the question is: what does this longer version do that the shorter doesn't, and why is it necessary in some cases (like today for me)?
Short way (e.g. with OpenSSL 1.1.0f and Apache 2.4.37):
openssl genrsa -out notEncodedPk.key 3072
openssl req -new -out website.csr -sha256 -key notEncodedPk.key
openssl x509 -req -in website.csr -days 365 -signkey notEncodedPk.key -out website.cert -outform PEM
genrsa generates a 3072 bit RSA-Key. (The system should be online for some time to have good data in /dev/(u)random for seeding.) There is no need to generate an encrypted PK (1) and then use rsa to remove the password afterwards. (Maybe earlier versions of the tools required a password?)
req creates the certificate signing request and uses the PK for the signature. Providing something like -sha256 for the digest is optional. (3) Provide your infos in the interactive questionare. Ensure to put your site domain in "Common name:", otherwise the Apache will throw a warning (AH01909) and browsers will throw an "invalid certificate" message because the URL/domain does not match the certificate data (2). Leave "A challange password:" empty.
Use x509 to create a self-signed certificate with -signkey (the subject is copied to issuer). Normally the command works on certificates but with -req it accepts a CSR as an input. Then use your PK for signing the certificate. (-outform and -days are optional, with 30 days as the default value for the latter.)
Problem source:
As user207421 already stated: req creates a CSR OR it creates a self-signed root-CA-like certificate, therefore the typical tutorial tip
openssl req -x509 -nodes -days 365 -newkey rsa:3072 -sha256 -keyout website.key -out website.cert
is short but normally not what you want. You can also compare created certificates with
openssl x509 -text -noout -in website.cert
In the certificate, created with the single-line command, you see a section "X509v3 extensions:" with "X509v3 Basic Constraints: critical CA:TRUE". This is exactly the Apache warning message.
Instead, if you create the certificate with the three steps, the "X509v3 extensions:" section is not included into the certificate.
Appendix:
(1) Securing the PK with a password is a good idea in most cases. If the PK is stored without encryption, make sure to restrict access to root. If you use a password, you have to use the -passout/-passin options, but be aware that a simple "x" does not work anymore because some OpenSSL tools require at least 4 characters (otherwise: "result too small/bad password read"). Additionally in Apache you have to use something like SSLPassPhraseDialog buildin to manually enter the required password for the PK (or even for all PKs/certs) during Apache startup.
(2) Anyway, browsers will display a warning for self-signed certificates.
(3) Using SHA-1 would be inadequate for such a large RSA-key. In general, it is a good idea to review your openssl.conf, e.g. in Debian 9 in /etc/ssl/openssl.conf, which contains various defaults, for example signer_digest = sha256.
In the Debian 9 file, you also find in the [req] section a line x509_extensions=v3_ca and this is the reason, why the req command in combination with the -x509 option adds the CA-related extension (basicContraints=critical,CA:true), if used in the single-line style to create a self-signed certificate.
Addidionally you might notice a comment-line # req_extensions=v3_req. Because this line is commented out (in Debian 9 default openssl.cnf), the simple usage of the req command does not include any extensions.
Note that you might use this line in a modified file to add Subject Alternative Name's to the certificate, e.g. so it can handle multiple (sub-)domains (normally a much better choice than using e wildcard in CN, e.g. *.example.com).
complete CA and SSL creation / setup help:
I created my own CA cert and used it to load into browser (as CA authority) and sign my self-created SSL cert for my Apache_on_ubuntu website.
steps:
generate my CA private key:
# openssl genrsa -des3 -out /etc/ssl/private/myCA.key 2048
generate root certificate: *****send myCA.pem to all desktop/client browsers.
# openssl req -x509 -days 5475 -new -nodes -key /etc/ssl/private/myCA.key -sha256 -out /etc/ssl/certs/myCA.pem
Install the root CA in firefox. (cp myCA.pem to windows box)
in firefox: options -> privacy_&_security -> view_certificates -> certificate_manager -> Authorities -> import
Creating CA-Signed Certificates for Your Sites
4.1: create website private key:
# openssl genrsa -out /etc/ssl/private/www.mywebsite.com.key 2048
4.2: create website CSR: Note: answers don’t need to match the CA cert ans.
# openssl req -new -key /etc/ssl/private/www.mywebsite.com.key -out /etc/ssl/private/www.mywebsite.com.csr
4.3: Create config file: config file is needed to define the Subject Alternative Name (SAN) extension. "method to match a domain name against a certificate – using the available names within the subjectAlternativeName extension"
# vi /etc/ssl/private/www.mywebsite.com.ext
...............I have not used the ext file option.....(for hosting multiple SSL sites and certs on same host)
4.4: Create the certificate:
# openssl x509 -req -in /etc/ssl/private/www.mywebsite.com.csr -CA /etc/ssl/certs/myCA.pem -CAkey /etc/ssl/private/myCA.key -CAcreateserial -out /etc/ssl/certs/www.mywebsite.com.crt -days 5475 -sha256
create ssl-conf file:
# cat /etc/apache2/conf-available/ssl-params.conf
# modern configuration, tweak to your needs
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
restart apache:
# systemctl restart apache2
Figure out which openssl.cnf you are using.
Select the correct section name that is doing your request.
Take out the CA:TRUE part (or change it to CA:FALSE) from the basicConstraint in the openssl.cnf that you are using.
Recreate the certificate the exact same way you did.
Your Apache webserver will no longer see a CA, much less a self-signed CA but just an ordinary self-signed certificate.
I had the same problem just today on Debian 9 stretch and I tried your solution to generate a new certificate using your method and it did not work. The warning in Apache was exactly the same.
I found out that the problem was that in my browser were stored other 6 certificates with the same FQDN. I erased the certificates and the problem has gone.
EDIT: Well, there's still the warning actually but at least everything is working.
openssl req creates a CSR or a CA root certificate. See the man page. It is not what you want. The second set of steps is correct.