diff --git a/docker/Dockerfile b/docker/Dockerfile index 5cf58d5..c9d49db 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,9 +21,8 @@ RUN mkdir -p /var/log/cert/ /run/php/ /sca/ && \ rsync \ ssmtp \ sudo && \ - sed -i -e '/listen =/ s/= .*/= 0.0.0.0:9000/' /etc/php7/php-fpm.d/www.conf && \ + sed -i -e '/listen =/ s/= .*/= 9000/' /etc/php7/php-fpm.d/www.conf && \ sed -i -e '/;pid =/ s/.*/pid = \/var\/run\/php-fpm.pid/' /etc/php7/php-fpm.conf && \ - echo "" >> /etc/php7/php-fpm.conf && \ chmod +x /entrypoint.sh /healthcheck.sh && \ ln -sf /dev/stderr /var/log/php7/error.log RUN apk add git && \ diff --git a/examples/httpd-local/README.md b/examples/httpd-local/README.md index e608e4d..c8d98e8 100644 --- a/examples/httpd-local/README.md +++ b/examples/httpd-local/README.md @@ -1,6 +1,6 @@ # Example: httpd + htpasswd -This Example shows how to use sca with httpd and ldap using docker. +This Example shows how to use sca with httpd and htpasswd using docker. ## Prepare setup diff --git a/examples/nginx-client-cert/README.md b/examples/nginx-client-cert/README.md new file mode 100644 index 0000000..f3548e4 --- /dev/null +++ b/examples/nginx-client-cert/README.md @@ -0,0 +1,58 @@ +# Example: nginx + client certificate + +This Example shows how to use sca with nginx and client certificate using docker. + +## Prepare setup + +1. Start system using `docker-compose up -d` +1. Add pfx [certificates](../shared/config-client-cert/) to your browser +1. Visit https://localhost +1. Login using one of the following credentials (Only cert-sync account exists at first): + +|Username|Type| +|---|---| +|cert-sync|admin| +|rainbow|admin| +|proceme|user| + +If something goes wrong, check the log using: +``` +docker logs -f nginx-client-cert_sca_1 +``` + +## Using sca + +_The `cert-sync` user should only be used for the first setup. Afterwards its best to create a dedicated account per user._ + +1. Login using the admin account `cert-sync`. +1. Create user `rainbow` as admin and user `proceme` as user at https://localhost/users#add +1. Add the server `nginx.example.com` and `httpd.example.com` at https://localhost/servers#add +1. Sca should be able to connect to both systems. You can verify this by checking whether there is an `Synced successfully` next to the servers. +1. Add the scripts [check_loport.sh](../scripts/check_loport.sh), [restart_httpd.sh](../scripts/httpd/restart_httpd.sh), [restart_nginx.sh](../scripts/nginx/restart_nginx.sh) and [status_pidfile.sh](../scripts/status_pidfile.sh) +1. Add two services called `nginx_8443` and `httpd_8444`. Both should use `check_loport.sh`, `status_pidfile.sh` and their respective restart script +1. Add the following two variables to both services. + * nginx_8443: + * Name: `PID_FILE` + * Value: `/run/nginx/nginx.pid` + * Name: `PORT` + * Value: `8443` + * httpd_8444: + * Name: `PID_FILE` + * Value: `/var/run/apache2/httpd.pid` + * Name: `PORT` + * Value: `8444` +1. Add the certificates from [certificates](../certificates) +1. Add two profiles. `nginx_demo` with `nginx.example.com` and `nginx_8443` as well as `httpd_demo` with `httpd.example.com` and `httpd_8444`. Both with certifcate `test1`. +1. Go to https://localhost/servers#list and use `Sync listed servers now`. After the confirmation it should only take a few seconds until both servers should be set to `Synced successfully`. +1. Visit https://localhost:8443/ and https://localhost:8444/ and take a look at the certificate it should be valid till 2020-01-01. Now visit [certificate test1](https://localhost/certificates/test1#migrate) and migrate it to `test2`. Afterwards synchronise the servers again. After reloading both pages, the certificate should now be valid till 2021-01-01. + +## Add access for new users + +1. Generate private key: `openssl genrsa -out .key 2048` +1. Generate certificate signing request: `openssl req -new -key .key -out .csr -subj "/CN="` +1. Generate certificate using the ca: `openssl x509 -req -in .csr -CA ca.pem -CAkey ca.key -CAcreateserial -out .pem -days 36500 -sha256` +1. Generate pfx: `openssl pkcs12 -export -out .pfx -inkey .key -in .pem -certfile ca.pem` + +## View synchronisation logs + +The Web UI only shows very rudimentary error messages. For more information connect to the php container using `docker exec -it nginx-client-cert_sca-php_1 /bin/ash`. In there, you can use the this command to follow the log file of the synchronisation daemon: `tail -f /var/log/cert/sync.log`. diff --git a/examples/nginx-client-cert/docker-compose.yml b/examples/nginx-client-cert/docker-compose.yml new file mode 100644 index 0000000..9edfeb8 --- /dev/null +++ b/examples/nginx-client-cert/docker-compose.yml @@ -0,0 +1,97 @@ +version: '2.2' +services: + test-nginx: + image: alpine + command: /bin/ash -c "(id cert-sync || adduser -h /var/local/cert-sync -S -D -s /bin/sh cert-sync) && mkdir -p /var/run/nginx/ && chmod 711 /var/local/cert-sync && cp /key /var/local/cert-sync/cert-sync && chown cert-sync:nogroup /var/local/cert-sync/cert-sync && chmod 644 /var/local/cert-sync/cert-sync && apk add openssh openssl nginx sudo && ssh-keygen -A && sed -i -e '/#StrictModes/ s/.*/StrictModes no/' /etc/ssh/sshd_config && sed -i -e '/AuthorizedKeysFile/ s/.*/AuthorizedKeysFile \/var\/local\/cert-sync\/%u/' /etc/ssh/sshd_config && passwd cert-sync -d test && passwd root -d test && echo 'cert-sync ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && /usr/sbin/sshd -D" + restart: always + expose: + - "22" + ports: + - "8443:8443" + depends_on: + - sca-php + volumes: + - ../shared/config-client-cert/cert-sync.pub:/key:ro + - ../shared/nginx-demo.conf:/etc/nginx/conf.d/default.conf:ro + networks: + net: + aliases: + - nginx.example.com + + test-httpd: + image: alpine + command: /bin/ash -c "(id cert-sync || adduser -h /var/local/cert-sync -S -D -s /bin/sh cert-sync) && chmod 711 /var/local/cert-sync && cp /key /var/local/cert-sync/cert-sync && chown cert-sync:nogroup /var/local/cert-sync/cert-sync && chmod 644 /var/local/cert-sync/cert-sync && apk add openssh openssl apache2 apache2-ssl sudo && ssh-keygen -A && sed -i -e '/#StrictModes/ s/.*/StrictModes no/' /etc/ssh/sshd_config && sed -i -e '/AuthorizedKeysFile/ s/.*/AuthorizedKeysFile \/var\/local\/cert-sync\/%u/' /etc/ssh/sshd_config && passwd cert-sync -d test && passwd root -d test && echo 'cert-sync ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && /usr/sbin/sshd -D" + restart: always + expose: + - "22" + ports: + - "8444:8444" + depends_on: + - sca-php + volumes: + - ../shared/config-client-cert/cert-sync.pub:/key:ro + - ../shared/httpd-demo.conf:/etc/apache2/conf.d/demo.conf:ro + networks: + net: + aliases: + - httpd.example.com + + mail: + image: mwader/postfix-relay + restart: always + environment: + - POSTFIX_myhostname=sca.example.de + - POSTFIX_mynetworks=0.0.0.0/0 + expose: + - "25" + networks: + - net + + sca-db: + image: mariadb + restart: always + environment: + - MYSQL_ROOT_PASSWORD=root-password + - MYSQL_DATABASE=sca-db + - MYSQL_USER=sca-user + - MYSQL_PASSWORD=password + volumes: + - db:/var/lib/mysql:rw + networks: + - net + + sca-php: + build: + context: ../../docker + restart: always + depends_on: + - sca-db + - mail + volumes: + - ../../:/sca/:rw + - ../shared/config-client-cert/:/sca/config/:rw + - ../shared/ssmtp.conf:/etc/ssmtp/ssmtp.conf:ro + - html:/public_html:rw + networks: + - net + + sca: + image: nginx:alpine + restart: always + ports: + - "443:443" + depends_on: + - sca-php + volumes: + - html:/sca/public_html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + - ../shared/config-client-cert/ca.pem:/ca.pem:ro + - ../shared/config-client-cert/ca.key:/ca.key:ro + networks: + - net + +networks: + net: +volumes: + html: + db: diff --git a/examples/nginx-client-cert/nginx.conf b/examples/nginx-client-cert/nginx.conf new file mode 100644 index 0000000..224b604 --- /dev/null +++ b/examples/nginx-client-cert/nginx.conf @@ -0,0 +1,38 @@ + +server { + listen 443 ssl; + server_name sca.example.com; + + root /sca/public_html; + index init.php; + + ssl_certificate /ca.pem; + ssl_certificate_key /ca.key; + + ssl_client_certificate /ca.pem; + ssl_verify_client optional; + + if ($ssl_client_verify != SUCCESS) { + return 403; + } + + location / { + try_files $uri $uri/ @php; + } + + location @php { + rewrite ^/(.*)$ /init.php/$1 last; + } + + location /init.php { + # Mitigate https://httpoxy.org/ vulnerabilities + fastcgi_param HTTP_PROXY ""; + fastcgi_pass sca-php:9000; + + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS on; + fastcgi_param X-SSL-CERT-DN $ssl_client_s_dn; + } +} diff --git a/examples/nginx-local/README.md b/examples/nginx-local/README.md index 0627267..3cbdfb5 100644 --- a/examples/nginx-local/README.md +++ b/examples/nginx-local/README.md @@ -1,6 +1,6 @@ # Example: nginx + htpasswd -This Example shows how to use sca with nginx and ldap using docker. +This Example shows how to use sca with nginx and htpasswd using docker. ## Prepare setup diff --git a/examples/shared/config-client-cert/ca.key b/examples/shared/config-client-cert/ca.key new file mode 100644 index 0000000..b159fe4 --- /dev/null +++ b/examples/shared/config-client-cert/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAw/KvOhxTDfj/4qKpWciY3ER+d9XW91tDj6OMWLGKP7bUjpnW +yJ2MiuINhM5FvCmS1C7G2hew0G735Dklo9ubjqih/VoodFZOH7C/ZV2WZcX5yfqL +w2eA1mT6a/XdtXc9AkNYjXQTv3HAW0nDHQjpoNYvz2Mzk7ricVtzNpn2H5YKQrDi +6O36185kLqz/MUq5uMlDZL8+Qc2uEUwJ5PUgRt2SiEtbUocKaqlwzl3Fw1bfuTsg +mPpuMszxQ3ZcVB/a4yNst08Gt6N5SQg9V/ZtHxwKhVDF/YxYXD3/jXaeWmPA+My5 +Z3SfvBPi35KAw66nett7tFea0tUkuLPchRks7wIDAQABAoIBACl1LVHUMFZienFP +DRmr8XuZGlDsc4D+VDsN1WHmnmOAoVRShDkJ7HRuA4I3ylVXTnOKOhRmsshG0jf9 +R1N14WG7mBpseuayY/EcikI+HNYHnyP22J9NRNsXnkw9Rt1gDw5thUzYZF07CgHG +h3ubKSMYbiEo0f7NqZ9mQ+gnhwWDcH3/l9DW6E1XHqtnnh3tda1YzUKu4hUutVrg +7CyHt+sAiVoipwBgRJvucSAKd/Z11E3/OLczLPdjizcYYtb6h+sWictC+sncUJo+ +scM1LKrGI5pfMXggwGb2Blxoxk8bg0FHGeeBRsVjkRzE4/tzY3vfBWi9dsT0wxcH +dygyjeECgYEA4FczEeTDlbPDJUkFLkMR2bYIcL/uftBF9bY+2Kv42/WrIAxa6CpU +ZAu207kDVODb1m+GGcI7SkebuxArWcfflobNx6HlbyU9N8PK4P2Ab+Aw29IE23RJ +6s0qXjI08pBWBc7eecSTUKVLtmfNc/zA4m5tufcuY8eP38erNB7WSUkCgYEA35m8 +/5IN8R5LWNh30pm+9NvAcT7c/iQCog+I+1v9/nqeTFQPchqo8F1XLWnrL7H3FvQM +25eR+meem+Et84f6uJH6Wa/RYwyZrwd8k3Jx2yDPXv0YOWuRXQ8M4rSX/PPIMeZH +GtkQYg5bWM7zAeo0IZiMmjJhm7r3uexe/BPQPHcCgYBp/Wi4dH9fU/3HuNcEw1VE +qbcVJejU6yuSahcJIT7DFS9TReMROyaB2fL31rpXOKFEj44oz1ZZwrk1yO7iTQc6 +w9enELsoJszkctGAOOScdqw+vtvinu9pSqNQu6G+VCkAN3tuxkvfVykNNb+go/Kx +SAD5UmoNZDR6QoiFaGhPWQKBgGwErRUZzbpp1CR7joci1ukbMACFSeVMKCl6L8ae +TjmdqtNc2Ila77oOnYrG3GMSKjfgdkWvF0pMvIa5ZaV8T1bSzop9MT4UXDn99+ST +KQWe+A7/XbB/kp3OI39xlusQKepRQJBrxEpafS6N6z9FkSGZvHLRW+4LiJk84zYL +rFFJAoGAGgUN9V/sy+u+JgKJHC8/nChDjCJvarh1Gv62Gq4M2JqKz7TQz1QI3oxa +pNqkUt4VeLVLPqxRU3mTIQymcD/A27AxKlSiCqFnpuLpRRsZA8UwlxxwbOylW/ZS +NwatYZ/4WT9lFKaBfa+vfdbzH8wTk65mLI9zy/G/UeWtU4ngpoA= +-----END RSA PRIVATE KEY----- diff --git a/examples/shared/config-client-cert/ca.pem b/examples/shared/config-client-cert/ca.pem new file mode 100644 index 0000000..f1ce2e9 --- /dev/null +++ b/examples/shared/config-client-cert/ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFTCCAf2gAwIBAgIUYOdgcuAxbSmgsOVGuAGje+IZSHQwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwOY2EuZXhhbXBsZS5jb20wIBcNMTkwNTEyMDgyMjEwWhgP +MjExOTA0MTgwODIyMTBaMBkxFzAVBgNVBAMMDmNhLmV4YW1wbGUuY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw/KvOhxTDfj/4qKpWciY3ER+d9XW +91tDj6OMWLGKP7bUjpnWyJ2MiuINhM5FvCmS1C7G2hew0G735Dklo9ubjqih/Voo +dFZOH7C/ZV2WZcX5yfqLw2eA1mT6a/XdtXc9AkNYjXQTv3HAW0nDHQjpoNYvz2Mz +k7ricVtzNpn2H5YKQrDi6O36185kLqz/MUq5uMlDZL8+Qc2uEUwJ5PUgRt2SiEtb +UocKaqlwzl3Fw1bfuTsgmPpuMszxQ3ZcVB/a4yNst08Gt6N5SQg9V/ZtHxwKhVDF +/YxYXD3/jXaeWmPA+My5Z3SfvBPi35KAw66nett7tFea0tUkuLPchRks7wIDAQAB +o1MwUTAdBgNVHQ4EFgQUMm+/8ikUlL2AScmi026LjQ6RX2MwHwYDVR0jBBgwFoAU +Mm+/8ikUlL2AScmi026LjQ6RX2MwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOCAQEACvjKjDk8bw0OuzKxyL+aWNcIIZdEj91uVad0k2U69NQ9Oq2TLf8X +hDNTSQzpx3ddMzV2H0qribCbOKMfgTiy3HJrsbFWpwnH0NhDNI75wQRyJzclxnKb +jMtbXKH/DooY9Znk01lcfnDVQ7xbk3hhDBH6YFubf5LGNO/B6SzUGnkV772YpA1k +LbW5lMpNRuwbPNooDBOyaNqPg0OqqACG3LPcrz0bGC9+OsNf0yF3hZyUjvhL9ZR0 +Zglle/a+kMORJ0LCSUJh3Nac8lb0pY1SxrRiUh0Y5TYsiIU82xNKwxQHRo9H/aIU +GDCpiPFJTF2Ulu1IJpAnci0MyT1vzOaErA== +-----END CERTIFICATE----- diff --git a/examples/shared/config-client-cert/ca.srl b/examples/shared/config-client-cert/ca.srl new file mode 100644 index 0000000..6eb3784 --- /dev/null +++ b/examples/shared/config-client-cert/ca.srl @@ -0,0 +1 @@ +26A3FAE83BED0F4CC887B0C103E6067F534A8BC2 diff --git a/examples/shared/config-client-cert/cert-sync b/examples/shared/config-client-cert/cert-sync new file mode 100644 index 0000000..5ec63cc --- /dev/null +++ b/examples/shared/config-client-cert/cert-sync @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqatsYGwteC4X+aIsNThluXZlh9SbdQTB3Zy8EoRcgKSEQdvw +sn56e3SLCTtLVGYCD7l5bTvjuJIG0VGKGG8ZYiESHQRBqm6t0zWEJMiOC0mUt3xW +4YJcVFhsk54WhVMWW7Irjx+wk9VbX4OellU0FZSjAs+hSernAOnVoSNXnSaC5hoq +98QWdlUwI+pOkKfyII4YYcSaPByNA/dx4Hy4cVqIaVIvDf4SiYHnqbCCZbSZcrQD +W/pLnq6s4e3LvYNaB2BWLlbad8YuJRBRSp9C3thV1EmYIDQdlk6sg0fkFGYi+jWF +WKatA0u7Krt5Dh3BeeNgFqegfsb3E1P8+gUp6QIDAQABAoIBADdzCIYylGGUXs18 +jIoQFl6YLPJJL0z88waj9GrwyvJX0clcQbtzzj+OhOnNcP7yH3ZYHTDvw6pMPuIl +jcYSeO8y86J8A6HZbgF6mecTjChwMaQNhK9KVTZTd5h+r8l+r+3juoyZxLFrpuL+ +NtPWoKD05JlEled8V2ZbBTAWRsnahepwwIdVK9kroK/Z/O91YXHxsKkW8tr0BPAR +mJNFOZnMxKymyXZ05J9pEq+q054nDHdpOcKF6z1dNuVVpPvtPlJ+BfNZqLcQ87/3 +4AiX7hFEyiRQw6v5JXUXH1IkNuD3BpjFMAr5/g1damsWXw+rkeYk5uS44Em46LeU +hk2J8ykCgYEA1B9eZEI/xpejcXDLTR7NihF2kk3YmiHjnqvVRPV97/JlMw1kmulb +ga1Y4y7YLv0TT2lWTZ9U5d+aIGTLqDd2fFvUYwAojeibf8TIPxWj47IK1sG7K7/4 +fwtxzuYJ6TmehwKFLMqxW/RN8bxmXZOQyc0vdbWdc30filRpdJfkyMMCgYEAzMQG +NIc3WObmaNzT1qlC7svba0VWpE7Tve9yUlfQUzqiEPMWKxCxoxiSdjxKh3ZeFaGM +vb+EvvHryZVwO6Ym8YO3c7Or96/38dlpzR2USPPsE/z7q4Ai7p8s2OnpUdTN+Sbz +rUvWFZD+aTTzYaPHiknGGE4oTt90ksXhsHKR9+MCgYBZchzIZBMmd7zI0go4K9v5 +82EvUjoazF2CA+07VdVT/79ipqwckngul10a9x7buYWR/9YPhzcyc3Y3YKlr9m81 +Azfswx1WsZYYa28RAtwH4ISniCuXyCxNzhKgbuoQ+WX6gjqL57CgGMVhQNNddCMi +pS31Ea8iCsno9608J+ymJwKBgAupDgFnwcsaOvy1top+0QTwLuqI5EovAvfJ7uSp +g39500jLzvNc3ADBoXWCMWxNXUY2EOGtSk3lUNwF2oJLD+So74VzMdPE/YWUL+Yy +TQNqgIMS4PH/Nf4IqnEfwN2cFK6ffTjdHK/Vtecf0Mw6m15QnSs9KCZ1qxnAkv2N +SKqpAoGBAKb7H7PEoBlC0SfrdQIoqKcHX62IEVSS/bzeKRgOF8dNGeJkMLJeOzgK +uAdi8XYReKp6xkMz2KArfR55nkqGmqPHa9czxLcRh0afdccjK4kEgiCA3sGz80Hp +2r7EBnR6AK5aBTNDnpv2TQdOCTed5QBwHUWUWlD36xDayW/+cehH +-----END RSA PRIVATE KEY----- diff --git a/examples/shared/config-client-cert/cert-sync.pfx b/examples/shared/config-client-cert/cert-sync.pfx new file mode 100644 index 0000000..8663f90 Binary files /dev/null and b/examples/shared/config-client-cert/cert-sync.pfx differ diff --git a/examples/shared/config-client-cert/cert-sync.pub b/examples/shared/config-client-cert/cert-sync.pub new file mode 100644 index 0000000..1a104d5 --- /dev/null +++ b/examples/shared/config-client-cert/cert-sync.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpq2xgbC14Lhf5oiw1OGW5dmWH1Jt1BMHdnLwShFyApIRB2/Cyfnp7dIsJO0tUZgIPuXltO+O4kgbRUYoYbxliIRIdBEGqbq3TNYQkyI4LSZS3fFbhglxUWGyTnhaFUxZbsiuPH7CT1Vtfg56WVTQVlKMCz6FJ6ucA6dWhI1edJoLmGir3xBZ2VTAj6k6Qp/IgjhhhxJo8HI0D93HgfLhxWohpUi8N/hKJgeepsIJltJlytANb+kuerqzh7cu9g1oHYFYuVtp3xi4lEFFKn0Le2FXUSZggNB2WTqyDR+QUZiL6NYVYpq0DS7squ3kOHcF542AWp6B+xvcTU/z6BSnp root@sca.example.com diff --git a/examples/shared/config-client-cert/config.ini b/examples/shared/config-client-cert/config.ini new file mode 100644 index 0000000..8e06098 --- /dev/null +++ b/examples/shared/config-client-cert/config.ini @@ -0,0 +1,129 @@ +; SCA Cert Authority config file +[web] +enabled = 1 +baseurl = https://sca.example.com +logo = /logo-header-itmettke.png +; footer may contain HTML. Literal & " < and > should be escaped as & +; " < $gt; +footer = 'Developed by Marc Mettke' + +[general] +; Use timeout --version to find out the current version +; used on e.g. debian +; timeout_util = GNU coreutils +; used on e.g. alpine +timeout_util = BusyBox + +[security] +; Determine what happens if multiple servers have the same SSH host key: +; 0: Allow sync to proceed +; 1: Abort sync of affected servers and report an error +; It is not recommended to leave this set to '0' indefinitely +host_key_collision_protection = 1 + + +; Hostname verification is a supplement to SSH host key verification for +; making sure that the sync process has connected to the server that it +; expected to. + +; Determine how hostname verification is performed: +; 0: Do not perform hostname verification +; 1: Compare with the result of `hostname -f` +; 2: Compare with /var/local/cert-sync/.hostnames, fall back to `hostname -f` +; if the file does not exist +; 3: Compare with /var/local/cert-sync/.hostnames, abort sync if the file +; does not exist +; The last option provides the most solid verification, as a server will only +; be synced to if it has been explicitly allowed on the server itself. +hostname_verification = 0 + +[email] +enabled = 1 +; The mail address that outgoing mails will be sent from +from_address = sca@example.com +from_name = "SCA Cert Authority system" +; Where to mail security notifications to +report_address = reports@example.com +report_name = "SCA Cert Authority reports" +; Where users should contact for help +admin_address = admin@example.com +admin_name = "SCA Cert Authority administrators" +; You can use the reroute directive to redirect all outgoing mail to a single +; mail address - typically for temporary testing purposes +;reroute = test@example.com + +[database] +; Connection details to the MySQL database +hostname = sca-db +port = 3306 +username = sca-user +password = password +database = sca-db + +[ldap] +enabled = 0 +; Address to connect to LDAP server +host = ldaps://ldap.example.com:636 +; Use StartTLS for connection security (recommended if using ldap:// instead +; of ldaps:// above) +starttls = 0 +; LDAP subtree containing USER entries +dn_user = "ou=users,dc=example,dc=com" +; LDAP subtree containing GROUP entries +dn_group = "ou=groups,dc=example,dc=com" +; Set to 1 if the LDAP library should process referrals. In most cases this +; is not needed, and for AD servers it can cause errors when querying the +; whole tree. +follow_referrals = 0 + +; Leave bind_dn empty if binding is not required +bind_dn = +bind_password = + +; User attributes +user_id = uid +user_name = cn +user_email = mail + +; If inactive users exist in your LDAP directory, filter with the following +; settings: +; Field to filter on: +;user_active = organizationalstatus +; Use *one* of user_active_true or user_active_false +; user_active_true means user is active if the user_active field equals its +; value +;user_active_true = 'current' +; user_active_false means user is active if the user_active field does not +; equal its value +;user_active_false = 'former' + +; Group membership attributes. Examples below are for typical setups: +; +; POSIX groups +; group_member = memberUid +; group_member_value = uid +; +; Group-of-names groups +; group_member = member +; group_member_value = dn +; +; Attribute of group where members are stored +group_member = memberUid +; User attribute to compare with +group_member_value = uid + +; Members of admin_group are given full admin access to SSH Key Authority web +; interface +admin_group_cn = sca-administrators + +[inventory] +; SCA Cert Authority will read the contents of the file /etc/uuid (if it +; exists) when syncing with a server. If a value is found, it can be used as a +; link to an inventory system. +; %s in the url directive will be replaced with the value found in /etc/uuid +;url = "https://inventory.example.com/device/%s" + +[gpg] +; SCA Cert Authority can GPG sign outgoing emails sent from the +; email.from_address. To do this it needs to know an appropriate key ID to use +;key_id = 0123456789ABCDEF0123456789ABCDEF01234567 diff --git a/examples/shared/config-client-cert/proceme.pfx b/examples/shared/config-client-cert/proceme.pfx new file mode 100644 index 0000000..c60cff3 Binary files /dev/null and b/examples/shared/config-client-cert/proceme.pfx differ diff --git a/examples/shared/config-client-cert/rainbow.pfx b/examples/shared/config-client-cert/rainbow.pfx new file mode 100644 index 0000000..c10e4a0 Binary files /dev/null and b/examples/shared/config-client-cert/rainbow.pfx differ diff --git a/migrations/004.php b/migrations/004.php new file mode 100644 index 0000000..6ccb089 --- /dev/null +++ b/migrations/004.php @@ -0,0 +1,8 @@ +database->query(" +ALTER TABLE `certificate` + ADD `signing_request` tinyint(1) unsigned NOT NULL DEFAULT '0', + ADD `csr` text NOT NULL DEFAULT '' +"); diff --git a/model/certificate.php b/model/certificate.php index 6fc1588..dc66f93 100644 --- a/model/certificate.php +++ b/model/certificate.php @@ -103,7 +103,18 @@ public function get_openssl_info() { exec('openssl pkey -pubout -in '.escapeshellarg($private_filename).' 2>/dev/null | openssl sha1', $private_modulus); unlink($private_filename); - if(empty($cert_modulus) || $cert_modulus != $fullchain_modulus || $fullchain_modulus != $private_modulus) { + if(!empty($this->csr)) { + $csr_filename = tempnam('/tmp', 'csr-test-'); + $csr_file = fopen($csr_filename, 'w'); + fwrite($csr_file, $this->csr); + fclose($csr_file); + exec('openssl req -pubkey -noout -in '.escapeshellarg($csr_filename).' 2>/dev/null | openssl sha1', $csr_modulus); + unlink($csr_filename); + } else { + $csr_modulus = $cert_modulus; + } + + if(empty($cert_modulus) || $cert_modulus != $fullchain_modulus || $fullchain_modulus != $private_modulus || $private_modulus != $csr_modulus) { throw new InvalidArgumentException("Certificate doesn't look valid"); } else if(count($serial) == 1 && count($enddate) == 1 && preg_match('|^serial=(.*)$|', $serial[0], $matches_fp) && @@ -116,6 +127,56 @@ public function get_openssl_info() { } } + /** + * Create Signing Request using openssl + */ + public function create_openssl_certificate_signing_request($subject, $key_type) { + $csr_filename = tempnam('/tmp', 'csr-test-'); + $private_filename = tempnam('/tmp', 'private-test-'); + + switch($key_type) { + case 'rsa8192': + exec('openssl genrsa -out '.escapeshellarg($private_filename).' 8192'); + break; + case 'rsa4096': + exec('openssl genrsa -out '.escapeshellarg($private_filename).' 4096'); + break; + case 'rsa2048': + exec('openssl genrsa -out '.escapeshellarg($private_filename).' 2048'); + break; + case 'ecdsa521': + exec('openssl ecparam -genkey -name secp521r1 -out '.escapeshellarg($private_filename)); + break; + case 'ecdsa384': + exec('openssl ecparam -genkey -name secp384r1 -out '.escapeshellarg($private_filename)); + break; + case 'ecdsa256': + exec('openssl ecparam -genkey -name secp256r1 -out '.escapeshellarg($private_filename)); + break; + case 'ed25519': + exec('openssl genpkey -algorithm Ed25519 -out '.escapeshellarg($private_filename)); + break; + default: + throw new InvalidKeyTypeException(); + break; + } + exec('openssl req -new -key '.escapeshellarg($private_filename).' -out '.escapeshellarg($csr_filename).' -subj '.escapeshellarg($subject), $output, $return_var); + if($return_var != 0) { + throw new InvalidCertificateSubject(); + } + + $this->private = file_get_contents($private_filename); + $this->cert = ""; + $this->fullchain = ""; + $this->signing_request = 1; + $this->csr = file_get_contents($csr_filename); + $this->serial = ''; + $this->expiration = date('Y-m-d H:i:s', time()); + + unlink($csr_filename); + unlink($private_filename); + } + /** * List all profiles using this certificate. * @param array $include list of extra data to include in response - currently unused @@ -168,3 +229,6 @@ public function list_dependent_profiles($include = array(), $filter = array()) { } } class CertificateInUseException extends Exception {} +class InvalidKeyTypeException extends Exception {} +class InvalidCertificateSubject extends Exception {} + diff --git a/model/certificatedirectory.php b/model/certificatedirectory.php index 035b3db..ba43137 100644 --- a/model/certificatedirectory.php +++ b/model/certificatedirectory.php @@ -9,10 +9,12 @@ class CertificateDirectory extends DBDirectory { */ public function add_certificate(Certificate $certificate) { global $event_dir; - $certificate->get_openssl_info(); + if(!$certificate->csr) { + $certificate->get_openssl_info(); + } try { - $stmt = $this->database->prepare("INSERT INTO certificate SET name = ?, private = ?, cert = ?, fullchain = ?, serial = ?, expiration = ?, owner_id = ?"); - $stmt->bind_param('ssssssd', $certificate->name, $certificate->private, $certificate->cert, $certificate->fullchain, $certificate->serial, $certificate->expiration, $certificate->owner_id); + $stmt = $this->database->prepare("INSERT INTO certificate SET name = ?, private = ?, cert = ?, fullchain = ?, serial = ?, expiration = ?, owner_id = ?, signing_request = ?, csr = ?"); + $stmt->bind_param('ssssssdds', $certificate->name, $certificate->private, $certificate->cert, $certificate->fullchain, $certificate->serial, $certificate->expiration, $certificate->owner_id, $certificate->signing_request, $certificate->csr); $stmt->execute(); $certificate->id = $stmt->insert_id; $stmt->close(); @@ -80,7 +82,7 @@ public function list_certificates($include = array(), $filter = array()) { $where = array(); $bind = array(""); foreach($filter as $field => $value) { - if($value) { + if($value || $value == 0) { switch($field) { case 'name': $where[] = "certificate.name REGEXP ?"; @@ -97,6 +99,11 @@ public function list_certificates($include = array(), $filter = array()) { $bind[0] = $bind[0] . "d"; $bind[] = $value; break; + case 'signing_request': + $where[] = "certificate.signing_request = ?"; + $bind[0] = $bind[0] . "d"; + $bind[] = $value; + break; } } } diff --git a/model/migrationdirectory.php b/model/migrationdirectory.php index d3d9677..94c2b43 100644 --- a/model/migrationdirectory.php +++ b/model/migrationdirectory.php @@ -6,7 +6,7 @@ class MigrationDirectory extends DBDirectory { /** * Increment this constant to activate a new migration from the migrations directory */ - const LAST_MIGRATION = 3; + const LAST_MIGRATION = 4; public function __construct() { parent::__construct(); diff --git a/requesthandler.php b/requesthandler.php index ca7488e..26b9ccb 100644 --- a/requesthandler.php +++ b/requesthandler.php @@ -28,6 +28,15 @@ require('views/error403.php'); die; } +} else if(isset($_SERVER['X-SSL-CERT-DN'])) { + $dn = $_SERVER['X-SSL-CERT-DN']; + preg_match('|CN=([^,/]*)|', $dn, $matches); + try { + $active_user = $user_dir->get_user_by_uid($matches[1]); + } catch(UserNotFoundException $ex) { + require('views/error403.php'); + die; + } } else { throw new Exception("Not logged in."); } diff --git a/templates/base.php b/templates/base.php index 5a49bf4..bdffff5 100644 --- a/templates/base.php +++ b/templates/base.php @@ -52,7 +52,7 @@ + get('certificate')->signing_request) { ?> +
+

Upload certificate

+ + get('active_user')->get_csrf_field(), ESC_NONE) ?> +
+ + +
+
+ + +
+ + +
+

Migrate

@@ -143,4 +166,5 @@
+
diff --git a/templates/certificates.php b/templates/certificates.php index 47ec78e..1dda255 100644 --- a/templates/certificates.php +++ b/templates/certificates.php @@ -1,7 +1,8 @@

Certificates

@@ -56,7 +57,35 @@
-

Add certificate

+

Add Signing Request

+
+ get('active_user')->get_csrf_field(), ESC_NONE) ?> +
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

Upload certificate

get('active_user')->get_csrf_field(), ESC_NONE) ?>
@@ -79,7 +108,7 @@
- +
diff --git a/templates/invalid_key_type.php b/templates/invalid_key_type.php new file mode 100644 index 0000000..eb41fee --- /dev/null +++ b/templates/invalid_key_type.php @@ -0,0 +1,4 @@ +

Invalid Script Type

+
+

"get('type'))?>" doesn't look like a valid key type. Please go back and try again.

+
diff --git a/templates/invalid_subject.php b/templates/invalid_subject.php new file mode 100644 index 0000000..23c79fb --- /dev/null +++ b/templates/invalid_subject.php @@ -0,0 +1,4 @@ +

Invalid Certifcate Subject

+
+

"get('subject'))?>" doesn't look like a valid certificate subject. Please go back and try again.

+
diff --git a/views/certificate.php b/views/certificate.php index 018c04a..20295f9 100644 --- a/views/certificate.php +++ b/views/certificate.php @@ -9,7 +9,34 @@ require('views/error403.php'); die; } -if(isset($_POST['delete_certificate'])) { +if(isset($_POST['upload_certificate'])) { + $cert = getParameterOrDie($_POST, 'cert'); + $fullchain = getParameterOrDie($_POST, 'fullchain'); + + $certificate->cert = $cert; + $certificate->fullchain = $fullchain; + $certificate->signing_request = 0; + + try { + $certificate->get_openssl_info(); + $certificate->update(); + $alert = new UserAlert; + $alert->content = 'Certificate \''.hesc($certificate->name).'\' successfully created.'; + $alert->escaping = ESC_NONE; + $active_user->add_alert($alert); + redirect('#view'); + } catch(CertificateAlreadyExistsException $e) { + $alert = new UserAlert; + $alert->content = 'Certificate \''.hesc($certificate->name).'\' is already known by SSL Cert Authority.'; + $alert->escaping = ESC_NONE; + $alert->class = 'danger'; + $active_user->add_alert($alert); + redirect('#add'); + } catch(InvalidArgumentException $e) { + $content = new PageSection('certificate_upload_fail'); + } +} +else if(isset($_POST['delete_certificate'])) { try { $certificate->delete(); $alert = new UserAlert; @@ -60,7 +87,7 @@ $content->set('admin', $active_user->admin); $content->set('filter', $filter); $content->set('certificate', $certificate); - $content->set('all_certificates', $certificate_dir->list_certificates()); + $content->set('all_certificates', $certificate_dir->list_certificates(array(), array("signing_request" => 0))); $content->set('profiles', $profiles); $content->set('log', $certificate->get_log()); $head = ''."\n"; diff --git a/views/certificates.php b/views/certificates.php index 4e10273..5b64f7f 100644 --- a/views/certificates.php +++ b/views/certificates.php @@ -1,5 +1,38 @@ name = $name; + $certificate->owner_id = $active_user->id; + + try { + $certificate->create_openssl_certificate_signing_request($subject, $key_type); + $certificate_dir->add_certificate($certificate); + $alert = new UserAlert; + $alert->content = 'Certificate \''.hesc($certificate->name).'\' successfully created.'; + $alert->escaping = ESC_NONE; + $active_user->add_alert($alert); + redirect('#add'); + } catch(CertificateAlreadyExistsException $e) { + $alert = new UserAlert; + $alert->content = 'Certificate \''.hesc($certificate->name).'\' is already known by SSL Cert Authority.'; + $alert->escaping = ESC_NONE; + $alert->class = 'danger'; + $active_user->add_alert($alert); + redirect('#add'); + } catch(InvalidKeyTypeException $e) { + error_log($e); + $content = new PageSection('invalid_key_type'); + $content->set('type', $key_type); + } catch(InvalidCertificateSubject $e) { + error_log($e); + $content = new PageSection('invalid_subject'); + $content->set('subject', $subject); + } +} else if(isset($_POST['upload_certificate'])) { $name = getParameterOrDie($_POST, 'name'); $private = getParameterOrDie($_POST, 'private'); $password = getParameterOrDie($_POST, 'password'); @@ -24,6 +57,8 @@ $certificate->cert = $cert; $certificate->fullchain = $fullchain; $certificate->owner_id = $active_user->id; + $certificate->signing_request = 0; + $certificate->csr = ''; try { $certificate_dir->add_certificate($certificate); diff --git a/views/profile.php b/views/profile.php index 7bec01c..6b23981 100644 --- a/views/profile.php +++ b/views/profile.php @@ -155,7 +155,7 @@ $content = new PageSection('profile'); $content->set('filter', $filter); $content->set('profile', $profile); - $content->set('all_certificates', $certificate_dir->list_certificates()); + $content->set('all_certificates', $certificate_dir->list_certificates(array(), array("signing_request" => 0))); $content->set('all_servers', $server_dir->list_servers()); $content->set('all_services', $service_dir->list_services()); $content->set('servers', $servers); diff --git a/views/profiles.php b/views/profiles.php index 093e2de..a862010 100644 --- a/views/profiles.php +++ b/views/profiles.php @@ -91,7 +91,7 @@ $content = new PageSection('profiles'); $content->set('filter', $filter); $content->set('profiles', $profiles); - $content->set('all_certificates', $certificate_dir->list_certificates()); + $content->set('all_certificates', $certificate_dir->list_certificates(array(), array("signing_request" => 0))); $content->set('all_servers', $server_dir->list_servers()); $content->set('all_services', $service_dir->list_services()); $head = ''."\n";