Skip to content

Instantly share code, notes, and snippets.

@lukebakken
Last active July 5, 2022 13:54
Show Gist options
  • Save lukebakken/ab4db1efa3e1847c7731 to your computer and use it in GitHub Desktop.
Save lukebakken/ab4db1efa3e1847c7731 to your computer and use it in GitHub Desktop.
Riak 2 / PAM / Certificates

Testing Notes

At this time only the protocol buffers client supports client certificates. HTTP is not supported.

Setup

Setting up a Root CA

It is necessary to set up a Root Certificate authority to be able to create and sign certificates.

References:

http://www.freebsdmadeeasy.com/tutorials/web-server/apache-ssl-certs.php

http://pages.cs.wisc.edu/~zmiller/ca-howto/

http://www.tldp.org/HOWTO/SSL-Certificates-HOWTO/x160.html

Steps:

  • Create openssl configuration file similar to this. This was copied from the "base" openssl configuration (/usr/local/openssl/openssl.cnf.sample on FreeBSD)
HOME        = .
RANDFILE    = $ENV::HOME/.rnd
oid_section = new_oids

[ new_oids ]
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7

[ ca ]
default_ca = CA_default

[ CA_default ]
dir              = .
certs            = $dir/certs
crl_dir          = $dir/crl
database         = $dir/index.db
new_certs_dir    = $dir/newcerts
certificate      = $dir/certs/cacert.pem
serial           = $dir/serial
crlnumber        = $dir/crlnumber
crl              = $dir/crl.pem
private_key      = $dir/private/cakey.pem# The private key
RANDFILE         = $dir/private/.rand
x509_extensions  = usr_cert
name_opt         = ca_default
cert_opt         = ca_default
default_days     = 3650
default_crl_days = 30
default_md       = sha1
preserve         = no
policy           = policy_match

[ policy_match ]
countryName            = match
stateOrProvinceName    = match
organizationName       = match
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

[ req ]
default_bits       = 2048
default_keyfile    = privkey.pem
distinguished_name = req_distinguished_name
attributes         = req_attributes
x509_extensions    = v3_ca
string_mask        = utf8only

[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = US
countryName_min             = 2
countryName_max             = 2
stateOrProvinceName         = State Name (full name)
stateOrProvinceName_default = WA
localityName                = Locality (eg, city)
0.organizationName          = Org Name (eg, company)
0.organizationName_default  = Basho Technologies
organizationalUnitName      = Org Unit Name (eg, section)
organizationalUnitName_default = CliServ
commonName                  = Common Name
commonName_max              = 64
emailAddress                = [email protected]
emailAddress_max            = 64

[ req_attributes ]
challengePassword     = A challenge password
challengePassword_min = 4
challengePassword_max = 20
unstructuredName      = An optional company name

[ usr_cert ]
basicConstraints       = CA:FALSE
nsComment              = "OpenSSL Generated Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage         = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = CA:true

[ crl_ext ]
authorityKeyIdentifier = keyid:always

[ proxy_cert_ext ]
basicConstraints       = CA:FALSE
nsComment              = "OpenSSL Generated Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
proxyCertInfo          = critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

[ tsa ]
default_tsa = tsa_config1

[ tsa_config1 ]
dir                    = ./demoCA
serial                 = $dir/tsaserial
crypto_device          = builtin
signer_cert            = $dir/tsacert.pem
certs                  = $dir/cacert.pem
signer_key             = $dir/private/tsakey.pem
default_policy         = tsa_policy1
other_policies         = tsa_policy2, tsa_policy3
digests                = md5, sha1
accuracy               = secs:1, millisecs:500, microsecs:100
clock_precision_digits = 0
ordering               = yes
tsa_name               = yes
ess_cert_id_chain      = no
  • Create a directory structure and some files. This is flexible based on your config file:
$ mkdir -p ~/ssl-ca/conf
$ cp openssl.conf ~/ssl-ca/conf
$ mkdir -p ~/ssl-ca/certs
$ mkdir -p ~/ssl-ca/newcerts
$ mkdir -p ~/ssl-ca/private
$ mkdir -p ~/ssl-ca/req
$ touch ssl-ca/index.db
$ touch ssl-ca/index.db.attr
$ echo '1000' > ssl-ca/serial
  • Create Root CA key and certificate
$ cd ~/ssl-ca
$ openssl req -new -x509 -days 3650 -extensions v3_ca -keyout private/cakey.pem -out certs/cacert.pem -config conf/openssl.conf
Generating a 2048 bit RSA private key
..........................................................................................................................................................................................+++
......................................................+++
writing new private key to 'private/cakey.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
...
...
...

The ssl-ca directory will now contain the following:

$ find .
.
./private
./private/cakey.pem
./newcerts
./conf
./conf/openssl.conf
./req
./certs
./certs/cacert.pem
./index.db
./serial
./index.db.attr
  • Create certificate request for riakuser user

The following will create a certificate and specify the cert subject on the command line. Note that the CN (common name) parameter must match the user name configured via riak-admin security add-user riakuser

[~/ssl-ca]$ openssl req -new -config conf/openssl.conf -nodes -out req/client-certificate-1.pem -keyout private/client-certificate-1-key.pem -subj '/C=US/ST=WA/O=Basho Technologies/OU=CliServ/CN=riakuser/[email protected]'
Generating a 2048 bit RSA private key
.........................................................................+++
....................+++
writing new private key to 'private/client-certificate-1-key.pem'
-----
  • Sign certificate request with Root CA

The following uses the -verbose flag to show extra output, and the -batch flag to not require any input.

[~/ssl-ca]$ openssl ca -verbose -batch -config conf/openssl.conf -out certs/client-certificate-1.pem -infiles req/client-certificate-1.pem
Using configuration from conf/openssl.conf
Enter pass phrase for ./private/cakey.pem:
0 entries loaded from the database
generating index
message digest is sha1
policy is policy_match
next serial number is 1000
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=US, ST=WA, O=Basho Technologies, OU=CliServ, CN=riakuser/[email protected]
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:cb:56:ec:ea:3a:a8:d5:c3:9f:ba:de:1c:fd:9d:
                    d5:b3:c4:24:f4:11:4e:29:6b:2e:8f:8f:e6:00:88:
                    fe:ce:dd:59:4e:91:5f:0f:24:b9:37:0e:c2:2e:f0:
                    60:ee:a1:97:f5:19:01:79:25:d2:9d:06:fb:d6:58:
                    8e:62:a1:c9:22:7e:88:51:92:58:38:b3:1d:35:04:
                    14:f6:8e:c3:40:0e:ce:67:92:ef:5d:fd:73:d4:6c:
                    3d:87:68:0e:22:e3:5e:bf:a5:6b:75:15:df:17:26:
                    c4:70:f6:7f:71:1e:57:46:38:9d:01:75:3f:00:3c:
                    b4:6f:eb:13:3e:2d:b5:8b:29:f5:d1:05:36:c6:9c:
                    96:87:73:e4:7c:69:d9:dd:2d:de:4a:cf:ef:0c:e9:
                    cb:7f:96:cc:82:df:66:27:b4:b8:54:7d:d1:fd:09:
                    62:2b:25:d6:b9:36:25:cb:16:ee:e9:89:eb:a1:d2:
                    66:b6:c7:08:9a:8d:35:aa:4a:12:dd:ab:38:6d:21:
                    ae:6c:78:af:2b:52:6e:2a:67:0f:a7:72:b7:aa:fd:
                    95:b0:e7:08:79:85:30:b2:9a:46:31:6e:cf:2c:ec:
                    06:6a:f4:ca:02:dd:53:80:2c:d8:09:f7:3c:bc:d8:
                    56:96:3c:ea:36:51:9f:03:b9:bd:38:93:76:38:07:
                    12:8d
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha1WithRSAEncryption
        ae:0c:d7:ff:d2:49:27:3f:80:58:b0:99:59:25:44:72:e1:a7:
        42:a8:cd:3d:61:89:17:bf:ce:88:d9:91:b5:cf:75:2f:fd:0d:
        d0:01:82:4e:b6:3c:49:fe:f9:0f:65:38:5b:33:fa:ad:d8:d2:
        a0:4b:ac:25:2b:ce:67:ae:ca:6f:c7:a8:ca:54:f7:2e:56:42:
        25:3f:f3:10:ff:58:81:87:e1:1b:d9:4c:09:1b:9d:8f:b3:e3:
        3e:f9:93:fb:77:bb:87:9a:07:38:95:c7:0c:5b:29:a1:41:33:
        04:18:d0:10:76:79:1c:da:54:91:3e:39:6f:dd:8a:39:18:59:
        e3:2f:6f:fb:f1:bf:f9:1e:be:dd:85:0c:4b:6b:ba:67:44:ff:
        ca:64:a4:98:48:28:35:ac:d9:6a:f4:eb:80:b0:19:8e:3d:36:
        36:99:73:dc:2b:66:87:09:6b:97:90:e4:19:d3:0d:9c:f6:31:
        f3:74:c1:df:90:7a:ce:5d:8f:15:3f:ef:b4:86:45:b6:66:da:
        ba:3d:56:a1:4e:df:db:4e:7f:06:1b:e9:c9:f9:a0:52:e4:8b:
        95:d7:8f:a1:99:13:69:7d:7c:b4:c4:48:99:0b:bf:36:43:f2:
        af:d3:25:1c:17:c2:af:45:8b:f3:e5:f0:1f:5c:42:09:6c:41:
        65:a6:99:bc
Check that the request matches the signature
Signature ok
The subject name appears to be ok, checking data base for clashes
Everything appears to be ok, creating and signing the certificate
Successfully added extensions from config
Certificate Details:
        Serial Number: 4096 (0x1000)
        Validity
            Not Before: Feb 24 18:46:10 2014 GMT
            Not After : Feb 22 18:46:10 2024 GMT
        Subject:
            countryName               = US
            stateOrProvinceName       = WA
            organizationName          = Basho Technologies
            organizationalUnitName    = CliServ
            commonName                = riakuser
            emailAddress              = [email protected]
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                7F:4C:CA:27:04:2C:4A:FB:D3:E3:D9:A0:4E:ED:8A:40:76:32:26:D7
            X509v3 Authority Key Identifier:
                keyid:38:37:C2:18:BE:CF:34:F1:DF:3D:43:96:10:82:77:9D:8E:AA:CC:4D

Certificate is to be certified until Feb 22 18:46:10 2024 GMT (3650 days)

Write out database with 1 new entries
writing new certificates
writing ./newcerts/1000.pem
Data Base Updated
  • Create certificate for Riak server

Assume that the server's host name is riak-node-1. The following commands will create a certificate that can be used by Riak on that server. The CN section of the subject must match the FQDN of the server or certificate verification will fail.

[~/ssl-ca]$ openssl req -new -config conf/openssl.conf -nodes -out req/riak-node-1.pem -keyout private/riak-node-1-key.pem -subj '/C=US/ST=WA/O=Basho Technologies/OU=CliServ/CN=riak-node-1/[email protected]'
Generating a 2048 bit RSA private key
......+++
...................+++
writing new private key to 'private/riak-node-1-key.pem'
-----
[~/ssl-ca]$ openssl ca -batch -config conf/openssl.conf -in req/riak-node-1.pem -out certs/riak-node-1-cert.pem
Using configuration from conf/openssl.conf
Enter pass phrase for ./private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 4097 (0x1001)
        Validity
            Not Before: Feb 24 20:26:44 2014 GMT
            Not After : Feb 22 20:26:44 2024 GMT
        Subject:
            countryName               = US
            stateOrProvinceName       = WA
            organizationName          = Basho Technologies
            organizationalUnitName    = CliServ
            commonName                = riak-node-1
            emailAddress              = [email protected]
        X509v3 extensions:
            X509v3 Basic Constraints:
                CA:FALSE
            Netscape Comment:
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier:
                DC:F1:64:06:2B:08:69:BE:5A:F8:E7:22:6C:CA:2B:5E:50:8B:56:E1
            X509v3 Authority Key Identifier:
                keyid:38:37:C2:18:BE:CF:34:F1:DF:3D:43:96:10:82:77:9D:8E:AA:CC:4D

Certificate is to be certified until Feb 22 20:26:44 2024 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
  • Using these certificates for Riak

You will want to generate a server certificate for each node in your Riak cluster (using a CN that matches each node's host name). Then, copy the generated certificate and the public part of the Root CA to the riak config directory (/etc/riak or /usr/local/etc/riak):

# scp certs/riak-node-1-cert.pem riak-node-1:/etc/riak
# scp certs/cacert.pem riak-node-1:/etc/riak
# scp private/riak-node-1-key.pem riak-node-1:/etc/riak

Then, in /etc/riak/riak.conf:

ssl.certfile = /etc/riak/riak-node-1-cert.pem
ssl.keyfile = /etc/riak/riak-node-1-key.pem
ssl.cacertfile = /etc/riak/cacert.pem

Once this has been configured on all nodes, do a rolling restart of Riak. Now you can use the client certificate you generated for riakuser to authenticate.

# riak-admin security add-user riakuser
# riak-admin security add-source riakuser 192.168.1.0/24 certificate
# riak-admin security print-sources
+--------------------+--------------+-----------+----------+
|       users        |     cidr     |  source   | options  |
+--------------------+--------------+-----------+----------+
|      riakuser      |192.168.1.0/24|certificate|    []    |
+--------------------+--------------+-----------+----------+

All that remains is to use this certificate via your Riak client. You can specify the full path to your client certificate, associated private key, and CA certificate file via these options:

{cacertfile, File} %% full path to CA cert file (certs/cacert.pem)
{certfile, File}   %% full path to client cert (certs/client-certificate-1.pem)
{keyfile, File}    %% full path to client cert private key (private/client-certificate-1-key.pem)

Ruby :auth data:

auth = {
    :cert => "/home/lbakken/Projects/basho/CorrugatedIron/tools/test-ca/certs/riakuser-client-cert.pem",           
    :key  => "/home/lbakken/Projects/basho/CorrugatedIron/tools/test-ca/private/riakuser-client-cert-key.pem",
    :ca_file => "/home/lbakken/Projects/basho/CorrugatedIron/tools/test-ca/certs/cacert.pem",
    :user => "riakuser",
    :password => ""
}

c = Riak::Client.new(:host => 'riak-test', :pb_port => 10017, :authentication => auth)

Testing Notes

Setup

  • FreeBSD 9.2 three-node cluster
  • riak-2.0.0pre14-FreeBSD-amd64.tbz

Docs used

http://www.freebsd.org/doc/en/articles/pam/article.html

Test setup

  • Installed https://github.com/tiwe-de/libpam-pwdfile via /usr/ports/security/pam_pwdfile

  • Installed /usr/ports/security/pamtester

  • Generate encrypted password using crypt() with: openssl passwd -crypt Pass1234

  • Created /usr/local/etc/pam_pwdfile.dat with contents:

    # cat /usr/local/etc/pam_pwdfile.dat
    lbakken:JgDQzNlO8ZGnU
    
  • Created /usr/local/etc/pam.d/riak PAM service definition file with these contents:

    # cat /usr/local/etc/pam.d/riak
    auth        required    /usr/local/lib/pam_pwdfile.so pwdfile=/usr/local/etc/pam_pwdfile.dat debug
    account     required    pam_permit.so
    session     required    pam_permit.so
    password    required    pam_deny.so
    

    See the PAM docs for the facilites used above. Technically, only auth is required in this file, but I included the other three facilities for documentation purposes. The auth section defines how authentication will be done. In this case, via the pam_pwdfile.dat file. Debug info is sent to syslog.

  • Test the riak PAM service using pamtester:

    # pamtester -v riak lbakken authenticate
    pamtester: invoking pam_start(riak, lbakken, ...)
    pamtester: performing operation - authenticate
    Password:
    pamtester: successfully authenticated
    

    You will also see the authentication logged by pamtester in /var/log/debug.log:

    pamtester: in openpam_dispatch(): calling pam_sm_authenticate() in /usr/local/lib/pam_pwdfile.so
    pamtester: in pam_get_user(): entering
    pamtester: in pam_get_item(): entering: PAM_USER
    pamtester: in pam_get_item(): returning PAM_SUCCESS
    pamtester: in pam_get_user(): returning PAM_SUCCESS
    pamtester: in pam_get_item(): entering: PAM_AUTHTOK
    pamtester: in pam_get_item(): returning PAM_SUCCESS
    pamtester: in pam_get_item(): entering: PAM_CONV
    pamtester: in pam_get_item(): returning PAM_SUCCESS
    pamtester: in pam_set_item(): entering: PAM_AUTHTOK
    pamtester: in pam_set_item(): returning PAM_SUCCESS
    pamtester: in pam_get_item(): entering: PAM_AUTHTOK
    pamtester: in pam_get_item(): returning PAM_SUCCESS
    pamtester: in pam_get_item(): entering: PAM_AUTHTOK
    pamtester: in pam_get_item(): returning PAM_SUCCESS
    pamtester: in openpam_dispatch(): /usr/local/lib/pam_pwdfile.so: pam_sm_authenticate(): success
    
  • Set up security in Riak. Note that the service=riak option to the pam source represents the default service name of riak. However, you can use this option to use another PAM service defined on your system. Or, in the case of pam.d, the recommended way to have multiple service names using the same definition is via file links.

    # riak-admin security enable
    # riak-admin security status
    Enabled
    # riak-admin security add-source all 127.0.0.1/32 pam service=riak
    # riak-admin security print-sources
    +--------------------+------------+----------+--------------------+
    |       users        |    cidr    |  source  |      options       |
    +--------------------+------------+----------+--------------------+
    |                    |127.0.0.1/32|   pam    |[{"service","riak"}]|
    |        all         |127.0.0.1/32|   pam    |[{"service","riak"}]|
    +--------------------+------------+----------+--------------------+
    # riak-admin security print-users
    +----------+---------------+----------------------------------------+------------------------------+
    | username |     roles     |                password                |           options            |
    +----------+---------------+----------------------------------------+------------------------------+
    | lbakken  |               |                                        |              []              |
    +----------+---------------+----------------------------------------+------------------------------+
    # riak-admin security print-user lbakken
    
    Inherited permissions
    
    +--------------------+----------+----------+----------------------------------------+
    |        role        |   type   |  bucket  |                 grants                 |
    +--------------------+----------+----------+----------------------------------------+
    
    Applied permissions
    
    +----------+----------+----------------------------------------+
    |   type   |  bucket  |                 grants                 |
    +----------+----------+----------------------------------------+
    |    *     |    *     |     riak_kv.list_keys, riak_kv.get     |
    +----------+----------+----------------------------------------+
    
  • Do some queries and you'll see PAM activity logged in /var/log/debug.log:

    $ curl -sk https://localhost:8443/types/tweets-type/buckets/tweets/keys/433767332201373696
    <html><head><title>401 Unauthorized</title></head><body><h1>Unauthorized</h1>Unauthorized<p><hr><address>mochiweb+webmachine web server</address></body></html>
    
    $ curl -u 'lbakken:Pass1234' -sk https://localhost:8443/types/tweets-type/buckets/tweets/keys/433767332201373696
    {"created_at":"2014-02-13T00:59:25Z","id":433767332201373696,"id_str":"433767332201373696",...
    

    Note that the process here is canola-port, which is the Basho library to use PAM (http://github.com/basho/canola):

    canola-port: in openpam_dispatch(): calling pam_sm_authenticate() in /usr/local/lib/pam_pwdfile.so
    canola-port: in pam_get_user(): entering
    canola-port: in pam_get_item(): entering: PAM_USER
    canola-port: in pam_get_item(): returning PAM_SUCCESS
    canola-port: in pam_get_user(): returning PAM_SUCCESS
    canola-port: in pam_get_item(): entering: PAM_AUTHTOK
    canola-port: in pam_get_item(): returning PAM_SUCCESS
    canola-port: in pam_get_item(): entering: PAM_CONV
    canola-port: in pam_get_item(): returning PAM_SUCCESS
    canola-port: in pam_set_item(): entering: PAM_AUTHTOK
    canola-port: in pam_set_item(): returning PAM_SUCCESS
    canola-port: in pam_get_item(): entering: PAM_AUTHTOK
    canola-port: in pam_get_item(): returning PAM_SUCCESS
    canola-port: in pam_get_item(): entering: PAM_AUTHTOK
    canola-port: in pam_get_item(): returning PAM_SUCCESS
    canola-port: in openpam_dispatch(): /usr/local/lib/pam_pwdfile.so: pam_sm_authenticate(): success
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment