diff --git a/.gitignore b/.gitignore index ad720a5..d0c8370 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /.idea/ /.vagrant/ +/deployment/*-from-vagrant.* +/mkcert_ca/ /tmp/ diff --git a/README.md b/README.md index 0c72f29..5f7f698 100644 --- a/README.md +++ b/README.md @@ -144,16 +144,11 @@ ansible-playbook to the Vagrant managed virtual machine. ```shell sudo apt install vagrant-libvirt virt-manager libvirt-clients vagrant up -vagrant ssh -- cat .local/share/mkcert/rootCA.pem | sudo tee /usr/local/share/ca-certificates/mkcert-vagrant-oidc.crt -sudo update-ca-certificates +CAROOT=$(pwd)/mkcert_ca mkcert -install ``` -## Finally - -*Note:* You may also want to configure your browser to trust the CA certificate -in `/usr/local/share/ca-certificates/mkcert-vagrant-oidc.crt`. If you do not -add this trust configuration you will get browser warnings for an unknown -certificate authority. +The last step installs the `mkcert` CA certificate in your user's browser trust +store. ## Testing your local setup @@ -162,7 +157,6 @@ After running `make` and `ansible-playbook`, Hydra and oidc-idp will both be run To run the rest of the components, in each of two new terminal windows, execute `oidc_app/demo-app` and `oidc_registration/cacert-oidc-registration`. - ### Test the authorization server Request the OpenID connect auto discovery information from Hydra diff --git a/Vagrantfile b/Vagrantfile index 00a1ac7..1bf57e6 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -27,5 +27,8 @@ Vagrant.configure("2") do |config| "authserver" => ["oidcbox"], "demoserver" => ["oidcbox"] } + ansible.extra_vars = { + mkcert_caroot: "/vagrant/mkcert_ca" + } end end diff --git a/deployment/group_vars/all.yml b/deployment/group_vars/all.yml index 1acb311..19b0bdc 100644 --- a/deployment/group_vars/all.yml +++ b/deployment/group_vars/all.yml @@ -1,2 +1,16 @@ --- hydra_home: /srv/hydra + +oidc_urls: + hydra_admin: + host: hydra.cacert.localhost + port: 4445 + hydra_public: + host: auth.cacert.localhost + port: 4444 + idp: + host: login.cacert.localhost + port: 3000 + demoapp: + host: app.cacert.localhost + port: 4000 diff --git a/deployment/host_vars/demoserver.yml b/deployment/host_vars/demoserver.yml new file mode 100644 index 0000000..6392816 --- /dev/null +++ b/deployment/host_vars/demoserver.yml @@ -0,0 +1,4 @@ +--- +demoapp_tls: + cert: "{{ cacert_home }}/etc/app.cacert.localhost.pem" + key: "{{ cacert_home }}/etc/app.cacert.localhost-key.pem" diff --git a/deployment/host_vars/localhost.yml b/deployment/host_vars/localhost.yml index 297fcfd..a065ca0 100644 --- a/deployment/host_vars/localhost.yml +++ b/deployment/host_vars/localhost.yml @@ -11,14 +11,14 @@ hydra_tls: # different random values encrypted via ansible-vault hydra_system_secret: "AczA+NZ25Ye9eAreglv5bo9XcND6uwBQHVUYCvPfwXo=" -register_tls: - cert: "{{ cacert_home }}/etc/register.cacert.localhost.pem" - key: "{{ cacert_home }}/etc/register.cacert.localhost-key.pem" - demoapp_tls: cert: "{{ cacert_home }}/etc/app.cacert.localhost.pem" key: "{{ cacert_home }}/etc/app.cacert.localhost-key.pem" +idp_tls: + cert: "{{ cacert_home }}/etc/idp.cacert.localhost.pem" + key: "{{ cacert_home }}/etc/idp.cacert.localhost-key.pem" + oidc_urls: hydra_admin: host: hydra.cacert.localhost @@ -33,8 +33,3 @@ oidc_urls: demoapp: host: app.cacert.localhost port: 4000 - register: - host: register.cacert.localhost - port: 5000 - -use_mkcert: true diff --git a/deployment/host_vars/oidcbox.yml b/deployment/host_vars/oidcbox.yml index 7915152..ecd7989 100644 --- a/deployment/host_vars/oidcbox.yml +++ b/deployment/host_vars/oidcbox.yml @@ -11,29 +11,10 @@ hydra_tls: # different random values encrypted via ansible-vault hydra_system_secret: "AczA+NZ25Ye9eAreglv5bo9XcND6uwBQHVUYCvPfwXo=" -register_tls: - cert: "{{ cacert_home }}/etc/register.cacert.localhost.pem" - key: "{{ cacert_home }}/etc/register.cacert.localhost-key.pem" +idp_tls: + cert: "{{ cacert_home }}/etc/idp.cacert.localhost.pem" + key: "{{ cacert_home }}/etc/idp.cacert.localhost-key.pem" demoapp_tls: cert: "{{ cacert_home }}/etc/app.cacert.localhost.pem" key: "{{ cacert_home }}/etc/app.cacert.localhost-key.pem" - -oidc_urls: - hydra_admin: - host: hydra.cacert.localhost - port: 4445 - hydra_public: - host: auth.cacert.localhost - port: 4444 - idp: - host: login.cacert.localhost - port: 3000 - demoapp: - host: app.cacert.localhost - port: 4000 - register: - host: register.cacert.localhost - port: 5000 - -use_mkcert: true diff --git a/deployment/roles/hydra_server/handlers/main.yml b/deployment/roles/hydra_server/handlers/main.yml index 63b31e8..25ec00d 100644 --- a/deployment/roles/hydra_server/handlers/main.yml +++ b/deployment/roles/hydra_server/handlers/main.yml @@ -1,7 +1,7 @@ --- - name: hydra_systemd_reload ansible.builtin.systemd: - state: started + state: restarted name: hydra daemon_reload: true enabled: true diff --git a/deployment/roles/hydra_server/tasks/main.yml b/deployment/roles/hydra_server/tasks/main.yml index a6b2945..7043c75 100644 --- a/deployment/roles/hydra_server/tasks/main.yml +++ b/deployment/roles/hydra_server/tasks/main.yml @@ -70,6 +70,8 @@ - name: Create Hydra key and certificate ansible.builtin.command: cmd: "mkcert -cert-file {{ hydra_cert_temp_dir.path }}/hydra.pem -key-file {{ hydra_cert_temp_dir.path }}/hydra.key.pem {{ oidc_urls.hydra_admin.host }} {{ oidc_urls.hydra_public.host }}" + environment: + CAROOT: "{{ mkcert_caroot | default(omit) }}" - name: Move Hydra certificate and key to target ansible.builtin.copy: @@ -89,30 +91,9 @@ path: "{{ hydra_cert_temp_dir.path }}" state: absent - when: use_mkcert and not hydra_cert_st.stat.exists + when: not hydra_cert_st.stat.exists become: false -- name: Copy Hydra key and certificate from inventory - block: - - - name: Copy Hydra certificate - ansible.builtin.copy: - dest: "{{ hydra_tls.cert }}" - owner: root - group: "{{ hydra_os_group }}" - mode: '0644' - content: "{{ hydra_tls.certdata }}" - - - name: Copy Hydra key - ansible.builtin.copy: - dest: "{{ hydra_tls.key }}" - owner: root - group: "{{ hydra_os_group }}" - mode: '0640' - content: "{{ hydra_tls.keydata }}" - - when: not use_mkcert - - name: Run Hydra SQL migrations ansible.builtin.command: cmd: "{{ hydra_home }}/bin/hydra migrate sql --yes --read-from-env --config {{ hydra_home }}/etc/hydra.yml" diff --git a/deployment/roles/oidc_demo_application/defaults/main.yml b/deployment/roles/oidc_demo_application/defaults/main.yml index a344905..58205fb 100644 --- a/deployment/roles/oidc_demo_application/defaults/main.yml +++ b/deployment/roles/oidc_demo_application/defaults/main.yml @@ -1,2 +1,4 @@ --- -# defaults file for roles/oidc_demo_application +cacert_os_user: cacert +cacert_os_group: cacert +cacert_home: /srv/cacert diff --git a/deployment/roles/oidc_demo_application/handlers/main.yml b/deployment/roles/oidc_demo_application/handlers/main.yml index 29cefff..cccd716 100644 --- a/deployment/roles/oidc_demo_application/handlers/main.yml +++ b/deployment/roles/oidc_demo_application/handlers/main.yml @@ -1,2 +1,7 @@ --- -# handlers file for roles/oidc_demo_application +- name: demoapp_systemd_reload + ansible.builtin.systemd: + state: restarted + name: cacert-demoapp + daemon_reload: true + enabled: true diff --git a/deployment/roles/oidc_demo_application/tasks/main.yml b/deployment/roles/oidc_demo_application/tasks/main.yml index d02ffeb..a6f371a 100644 --- a/deployment/roles/oidc_demo_application/tasks/main.yml +++ b/deployment/roles/oidc_demo_application/tasks/main.yml @@ -1,2 +1,166 @@ --- -# tasks file for roles/oidc_demo_application +- name: Manage /etc/hosts + blockinfile: + path: /etc/hosts + create: true + block: | + 127.0.0.1 localhost + 127.0.0.2 bookworm + ::1 localhost ip6-localhost ip6-loopback + ff02::1 ip6-allnodes + ff02::2 ip6-allrouters + + {{ oidc_urls.hydra_public.address | default(ansible_default_ipv4.address) }} {{ oidc_urls.hydra_public.host }} + 127.0.0.1 {{ oidc_urls.demoapp.host }} + +- name: Create CAcert group + ansible.builtin.group: + name: "{{ cacert_os_group }}" + state: present + system: true + +- name: Create CAcert user + ansible.builtin.user: + name: "{{ cacert_os_user }}" + group: "{{ cacert_os_group }}" + home: "{{ cacert_home }}" + state: present + system: true + +- name: Create CAcert directories + ansible.builtin.file: + path: "{{ cacert_home }}/{{ item.path }}" + owner: "{{ cacert_os_user }}" + group: "{{ cacert_os_group }}" + mode: "{{ item.mode }}" + state: directory + loop: + - { path: etc, mode: '0750' } + - { path: bin, mode: '0750' } + - { path: download, mode: '0750' } + +- name: Create session directory + ansible.builtin.file: + path: "{{ demoapp_session_path | default('/var/cache/cacert/sessions') }}" + owner: "{{ cacert_os_user }}" + group: "{{ cacert_os_group }}" + mode: "0750" + state: directory + +- name: Copy demo application binary + ansible.builtin.copy: + src: ../oidc_app/demo-app + dest: "{{ cacert_home }}/bin/cacert-oidcdemo" + owner: root + group: "{{ cacert_os_group }}" + mode: "0750" + +- name: Check whether certificate exists + ansible.builtin.stat: + path: "{{ demoapp_tls.cert }}" + register: demoapp_cert_st + +- name: Create demo application key and certificate with mkcert + block: + + - name: Create temporary directory for demo application key and certificate + ansible.builtin.tempfile: + prefix: "demoapp-cert." + state: directory + register: demoapp_cert_temp_dir + + - name: Create demo application key and certificate + ansible.builtin.command: + cmd: "mkcert -cert-file {{ demoapp_cert_temp_dir.path }}/demoapp.pem -key-file {{ demoapp_cert_temp_dir.path }}/demoapp.key.pem {{ oidc_urls.demoapp.host }}" + environment: + CAROOT: "{{ mkcert_caroot | default(omit) }}" + + - name: Move demo application certificate and key to target + ansible.builtin.copy: + src: "{{ demoapp_cert_temp_dir.path }}/{{ item.src }}" + dest: "{{ item.dest }}" + owner: root + group: "{{ cacert_os_group }}" + mode: "{{ item.mode }}" + remote_src: true + loop: + - {src: demoapp.pem, dest: "{{ demoapp_tls.cert }}", mode: '0644'} + - {src: demoapp.key.pem, dest: "{{ demoapp_tls.key }}", mode: '0640'} + become: true + + - name: Remove temporary directory + ansible.builtin.file: + path: "{{ demoapp_cert_temp_dir.path }}" + state: absent + + when: not demoapp_cert_st.stat.exists + become: false + +- name: Check whether configuration file exists + ansible.builtin.stat: + path: "{{ cacert_home }}/etc/cacert-demoapp.toml" + register: demoapp_config_st + +- name: Get credentials from existing file + block: + + - name: fetch existing configuration file + ansible.builtin.fetch: + src: "{{ demoapp_config_st.stat.path }}" + dest: demoapp_config-from-vagrant.toml + flat: true + + - name: set credential facts + ansible.builtin.set_fact: + demoapp_client_id: "{{ lookup('ansible.builtin.ini', 'client-id', section='oidc', file='demoapp_config-from-vagrant.toml') | from_json }}" + demoapp_client_secret: "{{ lookup('ansible.builtin.ini', 'client-secret', section='oidc', file='demoapp_config-from-vagrant.toml') | from_json }}" + demoapp_auth_key: "{{ lookup('ansible.builtin.ini', 'auth-key', section='session', file='demoapp_config-from-vagrant.toml') | from_json }}" + demoapp_enc_key: "{{ lookup('ansible.builtin.ini', 'enc-key', section='session', file='demoapp_config-from-vagrant.toml') | from_json }}" + + when: demoapp_config_st.stat.exists + +- name: Generate new credentials + block: + + - name: Create new client via Hydra admin API + ansible.builtin.uri: + url: "https://{{ oidc_urls.hydra_admin.host }}:{{ oidc_urls.hydra_admin.port }}/admin/clients" + method: "POST" + body: + client_name: "CAcert OIDC demo application" + redirect_uris: + - "https://{{ oidc_urls.demoapp.host }}:{{ oidc_urls.demoapp.port }}/callback" + post_logout_redirect_uris: + - "https://{{ oidc_urls.demoapp.host }}:{{ oidc_urls.demoapp.port }}/after-logout" + scope: "openid email profile groups" + body_format: "json" + headers: + Accept: "application/json" + Content-Type: "application/json" + status_code: [201] + register: hydra_response + + - name: Set credential facts + ansible.builtin.set_fact: + demoapp_client_id: "{{ hydra_response.json.client_id }}" + demoapp_client_secret: "{{ hydra_response.json.client_secret }}" + + when: not demoapp_config_st.stat.exists + +- name: Create demo application configuration + ansible.builtin.template: + src: demoapp_config.toml.j2 + dest: "{{ cacert_home }}/etc/cacert-demoapp.toml" + owner: root + group: "{{ cacert_os_group }}" + mode: '0640' + notify: demoapp_systemd_reload + +- name: Create demoapp systemd unit file + ansible.builtin.template: + src: cacert-demoapp.service.j2 + dest: /etc/systemd/system/cacert-demoapp.service + owner: root + group: root + mode: "0640" + notify: demoapp_systemd_reload diff --git a/deployment/roles/oidc_demo_application/templates/cacert-demoapp.service.j2 b/deployment/roles/oidc_demo_application/templates/cacert-demoapp.service.j2 new file mode 100644 index 0000000..8b29f2f --- /dev/null +++ b/deployment/roles/oidc_demo_application/templates/cacert-demoapp.service.j2 @@ -0,0 +1,14 @@ +[Unit] +Description=CAcert OpenID Connect demo application +After=network.target +Documentation=https://code.cacert.org/cacert/oidc-demo-app + +[Service] +ExecStart={{ cacert_home }}/bin/cacert-oidcdemo --conf "{{ cacert_home }}/etc/cacert-demoapp.toml" +WorkingDirectory={{ cacert_home }} +User={{ cacert_os_user }} +Group={{ cacert_os_group }} + +[Install] +WantedBy=multi-user.target + diff --git a/deployment/roles/oidc_demo_application/templates/demoapp_config.toml.j2 b/deployment/roles/oidc_demo_application/templates/demoapp_config.toml.j2 new file mode 100644 index 0000000..919d550 --- /dev/null +++ b/deployment/roles/oidc_demo_application/templates/demoapp_config.toml.j2 @@ -0,0 +1,19 @@ +[oidc] +client-id = "{{ demoapp_client_id }}" +client-secret = "{{ demoapp_client_secret }}" +server = "https://{{ oidc_urls.hydra_public.host }}:{{ oidc_urls.hydra_public.port }}/" + +[server] +name = "{{ oidc_urls.demoapp.host }}" +address = "{{ oidc_urls.demoapp.address | default(ansible_default_ipv4.address) }}" +port = {{ oidc_urls.demoapp.address | default("4000") }} +certificate = "{{ demoapp_tls.cert }}" +key = "{{ demoapp_tls.key }}" + +[session] +auth-key = "{{ demoapp_auth_key | default(lookup('community.general.random_string', length=64, base64=true)) }}" +enc-key = "{{ demoapp_enc_key | default(lookup('community.general.random_string', length=32, base64=true)) }}" +path = "{{ demoapp_session_path | default('/var/cache/cacert/sessions') }}" + +[log] +level = "trace" diff --git a/deployment/roles/oidc_idp/handlers/main.yml b/deployment/roles/oidc_idp/handlers/main.yml index 0901f0e..3eb34e2 100644 --- a/deployment/roles/oidc_idp/handlers/main.yml +++ b/deployment/roles/oidc_idp/handlers/main.yml @@ -1,7 +1,7 @@ --- - name: idp_systemd_reload ansible.builtin.systemd: - state: started + state: restarted name: cacert-idp daemon_reload: true enabled: true diff --git a/deployment/roles/oidc_idp/tasks/main.yml b/deployment/roles/oidc_idp/tasks/main.yml index 202912c..fbb7d47 100644 --- a/deployment/roles/oidc_idp/tasks/main.yml +++ b/deployment/roles/oidc_idp/tasks/main.yml @@ -50,6 +50,8 @@ - name: Create IDP key and certificate ansible.builtin.command: cmd: "mkcert -cert-file {{ idp_cert_temp_dir.path }}/idp.pem -key-file {{ idp_cert_temp_dir.path }}/idp.key.pem {{ oidc_urls.idp.host }}" + environment: + CAROOT: "{{ mkcert_caroot | default(omit) }}" - name: Move IDP certificate and key to target ansible.builtin.copy: @@ -69,30 +71,9 @@ path: "{{ idp_cert_temp_dir.path }}" state: absent - when: use_mkcert and not idp_cert_st.stat.exists + when: not idp_cert_st.stat.exists become: false -- name: Copy IDP key and certificate from inventory - block: - - - name: Copy IDP certificate - ansible.builtin.copy: - dest: "{{ idp_tls.cert }}" - owner: root - group: "{{ cacert_os_group }}" - mode: '0644' - content: "{{ idp.server_certificate_data }}" - - - name: Copy IDP key - ansible.builtin.copy: - dest: "{{ idp_tls.key }}" - owner: root - group: "{{ cacert_os_group }}" - mode: '0640' - content: "{{ idp.server_key_data }}" - - when: not use_mkcert - - name: Copy client CA certificates ansible.builtin.copy: dest: "{{ idp_tls.client_cas }}" @@ -101,6 +82,28 @@ mode: '0640' content: "{{ idp.client_certificate_data }}" +- name: Check whether configuration file exists + ansible.builtin.stat: + path: "{{ cacert_home }}/etc/cacert-idp.toml" + register: idp_config_st + +- name: Get credentials from existing file + block: + + - name: fetch existing configuration file + ansible.builtin.fetch: + src: "{{ idp_config_st.stat.path }}" + dest: idp_config-from-vagrant.toml + flat: true + + - name: set credential facts + ansible.builtin.set_fact: + idp_csrf_key: "{{ lookup('ansible.builtin.ini', 'csrf.key', section='security', file='idp_config-from-vagrant.toml') | from_json }}" + idp_auth_key: "{{ lookup('ansible.builtin.ini', 'auth-key', section='session', file='idp_config-from-vagrant.toml') | from_json }}" + idp_enc_key: "{{ lookup('ansible.builtin.ini', 'enc-key', section='session', file='idp_config-from-vagrant.toml') | from_json }}" + + when: idp_config_st.stat.exists + - name: Create IDP configuration ansible.builtin.template: src: idp_config.toml.j2 diff --git a/deployment/roles/oidc_idp/templates/idp_config.toml.j2 b/deployment/roles/oidc_idp/templates/idp_config.toml.j2 index 0283eac..a5d2029 100644 --- a/deployment/roles/oidc_idp/templates/idp_config.toml.j2 +++ b/deployment/roles/oidc_idp/templates/idp_config.toml.j2 @@ -8,5 +8,12 @@ port = {{ oidc_urls.idp.address | default("3000") }} certificate = "{{ idp_tls.cert }}" key = "{{ idp_tls.key }}" +[session] +auth-key = "{{ idp_auth_key | default(lookup('community.general.random_string', length=64, base64=true)) }}" +enc-key = "{{ idp_enc_key | default(lookup('community.general.random_string', length=32, base64=true)) }}" + [admin] url = "https://{{ oidc_urls.hydra_admin.address | default("hydra.cacert.localhost") }}:{{ oidc_urls.hydra_admin.port | default("3000") }}" + +[log] +level = "trace" diff --git a/deployment/roles/prepare_devtools/tasks/main.yml b/deployment/roles/prepare_devtools/tasks/main.yml index b35e34f..530e861 100644 --- a/deployment/roles/prepare_devtools/tasks/main.yml +++ b/deployment/roles/prepare_devtools/tasks/main.yml @@ -11,7 +11,8 @@ - name: Install mkcert CA ansible.builtin.command: cmd: "mkcert -install" + environment: + CAROOT: "{{ mkcert_caroot | default(omit) }}" changed_when: false - when: use_mkcert become: false diff --git a/oidc_idp b/oidc_idp index a5c583f..9aeca21 160000 --- a/oidc_idp +++ b/oidc_idp @@ -1 +1 @@ -Subproject commit a5c583f1f65cf5a09054ad7249c451551089cd0f +Subproject commit 9aeca21faa2db96ecd359e26eb4dc392d7c6bf1a