Saturday, August 30, 2025

Docker: Setting up Terraform, Ansible, Nginx and Keycloak with TLS via Step-Ca

 

Docker: Setting up Terraform, Ansible, Nginx and Keycloak with TLS via Step-Ca


I wanted to learn more about authentication in web apps and the Keycloak project seemed an ideal open source way for hands-on experimentation. What followed was a docker project to include various components to achieve this. I have already decided on a template approach to my docker projects and various links here of other templated setups.


https://www.rjruss.info/2025/03/setting-up-sap-web-dispatcher-with-tls.html

https://www.rjruss.info/2024/11/docker-setting-up-prometheus-grafana.html

https://www.rjruss.info/2024/11/docker-setting-up-gitea-with-postgres.html


Using my docker template approach I set out for a simple Hello World type project of calling an Ansible playbook and a Terraform configuration with a very simple authenticated web app via a Keycloak setup. The authentication is where Keycloak is required.  Reading further into Ansible and Terraform it would get more complicated to actually move beyond Hello World in how to control such IaC platforms via custom web calls. That didn't put me off :) I set up both to be used independently when connecting the containers themselves. The python code was “vibe coded” with the help of Gemini, and took a lot of troubleshooting and breaking down to individual steps to get it to work. E.g. At one point it was luck that I was getting the right key for the tokens and debug steps helped, but eventually I worked out how to stabilise the code with the help of Gemini. It depends on my next projects if I want to  further vibe code the web calls, but the Keycloak knowledge was the thing I started the whole thing for and it proved very useful.


The following output is the end result of building the docker compose project. (Read on for the pre-reqs before even getting to this point).

 

Ansible output extract

..

PLAY [Hello World Playbook] ****************************************************


TASK [Print Hello World] *******************************************************

ok: [localhost] => {

    "msg": "Hello, World!"

}

..


Terraform output extract

Plan: 1 to add, 0 to change, 0 to destroy.


Changes to Outputs:

  + hello_world = "Hello, World! from Terraform"

null_resource.hello: Creating...

null_resource.hello: Provisioning with 'local-exec'...

null_resource.hello (local-exec): Executing: ["/bin/sh" "-c" "echo Hello, world from Terraform!"]

null_resource.hello (local-exec): Hello, world from Terraform!

null_resource.hello: Creation complete after 0s [id=4932893277560461013]


Read previous blogs on more of the background to my use of Docker but to highlight. Again the project got complicated via some cyber security reasons around storing passwords in clear text and using TLS all over the place. Some security frameworks state that any clear password is an instant fail/breach. And personally and from security frameworks TLS is ideal in local networks.


Step 1 setup local user and directory

Creating a dedicated docker user in this example dockeruser1 - if this is changed the .env file needs to be adapted in step 2


The directory where the downloaded project is stored can be adapted as well.

The example uses  /srv/docker-config

Example using the name of the downloaded zip as ansible-terraform-nginx-keycloak-postgres-stepca-docker.zip



sudo su - 

useradd -m -s /bin/bash -u 1245 dockeruser1

mkdir /srv/docker-config

cd /srv/docker-config


unzip {downloaded zip file} -d .


cd /srv/docker-config/ansible-terraform-nginx-keycloak-postgres-stepca-docker

./initialGitHubScript.sh





Step 2  Adapt the .env file

Change bold entries if required and ignore any other settings in the .env file

Local docker user and group needs to match the user created in step 1



# check Env values

#LOCAL_SETUP - used by first_setup.sh scripts

LOCAL_DOCKER_USER=dockeruser1

LOCAL_DOCKER_GROUP=dockeruser1

LOCAL_DOCKER_VOLUME_DIR=/docker1

DOMAIN=rjruss.org

BASE_HOST=hawarch-

AT_SHARED_ENV_GROUP=5501

...

AT_APP_STEP_PORT=9006

AT_PROV_NAME=baseprov

AT_AUTH_NAME=basestepCA

AT_RCOUNT=1

AT_RDAY=Sunday

#Initial certificate lieftime in hours

AT_APP_CERT_DUR=24h

#Initial expiry check in %

AT_APP_EXP_CHECK=80%

AT_ROOTC=GB

AT_ROOTCA_VALID=1825

AT_RSLEEP=10m

AT_RTIME="07:30:00"

AT_FIXED_CERT_RENEW_TIME="YES"

AT_BUFFER_CERT_HOURS=2

AT_KEYCLOAK_PORT=44330

AT_KEYCLOAK_PROXY_HOST=keycloak.${DOMAIN}

AT_KEYCLOAK_PROXY_PORT=44340


AT_PROXY_HOST=proxy.${DOMAIN}

AT_ANSIBLE_PROXY_HOST=ansible.${DOMAIN}

AT_TERRAFORM_PROXY_HOST=terraform.${DOMAIN}





The above would be allow the services to be accessed 

  1. Via the docker user

Adapt the dockeruser1 - group dockeruser1 is used by default

  1. Via the shared group 

Adapt the group id of 5501 a new group will be created with that gid

  1. Via a new directory/volume (it will be created if it does not exist)

Adapt the /docker1

  1. Via the following domain, hostname and ports

rjruss.org = this needs to be adapted to the required domain

hawarch- = is the base pre-fix of all local hosts that are not part of the proxy

keycloak.${DOMAIN} = resolved via nginx settings to point to keycloak

proxy.${DOMAIN} = resolved via nginx settings to point to test httpbin site (for testing)

ansible.${DOMAIN} = resolved via nginx settings to point to Ansible

terraform.${DOMAIN} = resolved via nginx settings to point to Terraform

  1. Adapt Step CA

AT_APP_STEP_PORT=9006 # Port used by Step CA

These parameters control the name of the STEP services (root certificate name)

AT_PROV_NAME=baseprov

AT_AUTH_NAME=basestepCA

  1. Adapt certificate renewal process sets the certificate renewal time, useful to set the AT_RDAY as tomorrow and AT_RTIME at an appropriate renewal time

AT_RCOUNT=1

AT_RDAY=Sunday

#Initial certificate lieftime in hours

AT_APP_CERT_DUR=24h

#Initial expiry check in %

AT_APP_EXP_CHECK=80%

AT_ROOTC=GB

AT_ROOTCA_VALID=1825

AT_RSLEEP=10m

AT_RTIME="07:30:00"

AT_FIXED_CERT_RENEW_TIME="YES"

AT_BUFFER_CERT_HOURS=2



(postgres is not open to connections other than on the docker network and uses the standard postgres port 5432 inside the docker network)


Step 3 adapt the age related password files

The age (see step 4) command is used to store passwords in an encrypted file on a docker volume. These can be shared between containers via the bash scripts in the base docker container build.


These .*.info files should be deleted ***after completing the step 4 process***


#Postgres DB connection is certificate and password based (password can be used if postgres config adapted with the required password setup)

Step 4 Create Volumes, Networks and encrypted passwords


*** postgress password is here but not used

*** LDAP_PW is for reference and there is a script to configure this in the Keycloak container but not used by default

*** PW in the Keycloak file is the password for the new admin user created see later screenshots and info

*** WEB_ANSIBLE & WEB_TERRAFORM are the passwords required to authenticate to run the playbook / configuration via the web call (see example test scripts below for this). 

*** PW in Step file is for the certificate generation which is password protected


./.POST.info

POSTGRES_PASSWORD="testing!123"


./.KEYCLOAK.info

PW="testing123"

LDAP_PW="testing123!"

WEB_ANSIBLE="ansible123"

WEB_TERRAFORM="terraform123"



./.STEP.info

PW="largesmallgreat!"








Following script installs operating system pre-req packages, volumes, networks and encrypts the passwords.


./first_setup.sh






After running the script the following volumes and network will be available.


 docker volume ls |grep at

local     at_automateit_ansible_vol1

local     at_automateit_info_vol1

local     at_automateit_keycloak_vol1

local     at_automateit_keys_vol1

local     at_automateit_nginx_vol1

local     at_automateit_postgres_vol1

local     at_automateit_step_vol1

local     at_automateit_terraform_vol1




  docker network ls |grep at

710c2f355fcd   at-automateit-net1   bridge    local




**** delete these password files  ****ENSURE YOU KNOW THE PASSWORDS before deleting**** 

ls -a .*.info


rm -rf .KEYCLOAK.info  .POST.info  .STEP.info  .TEST.info




Step 5 build and run it


Network DNS resolution checks

Before building the docker compose project it is essential to check that the proxy hosts and dedicated hosts resolve as required.


./check_hosts.sh

SUCCESSFUL checks**

-   SUCCESS : ansible2.rjruss.org resolved

-   SUCCESS : keycloak2.rjruss.org resolved

-   SUCCESS : proxy2.rjruss.org resolved

-   SUCCESS : terraform2.rjruss.org resolved

-   SUCCESS : hawarch-keycloak.rjruss.org resolved

-   SUCCESS : hawarch-step.rjruss.org resolved


WARNING checks***

-   WARNING : hawarch-ansible.rjruss.org not resolving maybe not an issue as only allocated once so maybe no dependancies

-   WARNING : hawarch-nginx.rjruss.org not resolving maybe not an issue as only allocated once so maybe no dependancies

-   WARNING : hawarch-postdb.rjruss.org not resolving maybe not an issue as only allocated once so maybe no dependancies

-   WARNING : hawarch-terra.rjruss.org not resolving maybe not an issue as only allocated once so maybe no dependancies


ERROR checks




**SUCCESSFUL checks

The above example is from a test server to confirm this works on another server :)

The following hosts need to resolve to make this setup work


Proxy related hosts (resolved via nginx connection)


ansible2.rjruss.org resolved

keycloak2.rjruss.org resolved

proxy2.rjruss.org resolved

terraform2.rjruss.org resolved


Direct related hosts that need to succeed on the resolution


hawarch-keycloak.rjruss.org resolved

hawarch-step.rjruss.org resolved


***WARNING checks

The warnings are for future work or maybe not used. The hosts are part of the template based approach and for direct connections to the containers 


su - dockeruser1

cd /srv/docker-config/ansible-terraform-nginx-keycloak-postgres-stepca-docker

./build-it-and-run-it.sh



Could take a while depending on the server/computer performance! as it builds quite a but


Wait until you see the keycloak container logs indicating the health check is UP

Step 6 Run the test Ansible and Terraform scripts 

Run “test-ansible.sh” and “test-terraform.sh” to confirm the setup is working as expected. The scripts download the required Step based CA root certificate locally to the docker host and then calls the containers via the nginx proxy. Take a look at the scripts for the setup.


Start a new terminal session

su - dockeruser1

cd /srv/docker-config/ansible-terraform-nginx-keycloak-postgres-stepca-docker/

#Run the Ansible test script




./test_ansible.sh

PLAY [Hello World Playbook] ****************************************************


TASK [Print Hello World] *******************************************************

ok: [localhost] => {

    "msg": "Hello, World!"

}


PLAY RECAP *********************************************************************

localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0



The script calls a python web app running on the ansible container via the nginx proxy to get the token from Keycloak. The using the token calls Ansible again to run the playbook


A successful call is shown above with the “Hello, World!” output


#Run the Terraform test script



./test_terraform.sh


Terraform used the selected providers to generate the following execution

plan. Resource actions are indicated with the following symbols:

  + create


Terraform will perform the following actions:


  # null_resource.hello will be created

  + resource "null_resource" "hello" {

      + id = (known after apply)

    }


Plan: 1 to add, 0 to change, 0 to destroy.


Changes to Outputs:

  + hello_world = "Hello, World! from Terraform"

null_resource.hello: Creating...

null_resource.hello: Provisioning with 'local-exec'...

null_resource.hello (local-exec): Executing: ["/bin/sh" "-c" "echo Hello, world from Terraform!"]

null_resource.hello (local-exec): Hello, world from Terraform!

null_resource.hello: Creation complete after 0s [id=4932893277560461013]


Apply complete! Resources: 1 added, 0 changed, 0 destroyed.


Outputs:


hello_world = "Hello, World! from Terraform"




Using the same principal as the Ansible setup but calling the Terraform container to run the configuration. 

A successful call is shown above with the Hello, World! from Terraform output


Certificate Renewal Control Script(s)


./sleep-control.sh display

./sleep-control.sh change_check_days_to_expire 1

./sleep-control.sh sleep_interval 2m

#below command sets the certificate check to be at 18:30 today *time should be in the future

./sleep-control.sh change  18:30 $(date +%A)

./sleep-control.sh renew_certs_days_length 1

./sleep-control.sh display



The above scripts will set the certificate renewal to the following settings


#The date and time the certificate check will take place

Wake up time to check certificate : 30-08-2025 18:30:00


#The controlling scripts will sleep every 2 minutes before checking date and time for 

# certificate renewal

Sleep interval : 2m


#Not used in this project. This is an option to check that the certificate has X days left

Days remaining for certificate expiry check : 1


#This is where Step CA command will check that the certificate still valid 

Percentage for certificate expiry check : 80%


#Length of the renewed certificate - 

Length in days a new certificate is valid for : 1




Appendix

A: General Information: Keycloak 


To access the keycloak via the web admin pages you need the Step root certificate on your host computer. A helper script as follows is setup to cut and paste the command to get this on a windows computer via powershell.


Run,


 ./display_web_urls.sh

 ## automateit-step-run

 ## STEP powershell to trust certificate - added as a trusted root so use it on that understanding

downloading step CA certificate to /var/tmp/stepCA.pem

Successfully copied 2.56kB to /var/tmp/stepCA.pem


$B64="-----BEGIN CERTIFICATE-----

……..deleted

-----END CERTIFICATE-----"

$B64 | Out-File .\WEBSTEP.crt

Import-Certificate  -FilePath .\WEBSTEP.crt  -CertStoreLocation cert:\CurrentUser\Root


I have enclosed the powershell in the table to run on a windows computer via powershell prompt


Install step-ca root certificate on host windows 



Answer YES at the prompt


I have a homelab Hyper-V landscape with a windows DNS host, and it is important to set up dns resolution for all the proxy hosts in this project



For my local windows based P.C I updated the host file with the proxy hosts from earlier.

My example I need the Keycloak proxy URL and Nginx Proxy port e.g. 


https://keycloak2.rjruss.org:44340/



The new admin user is hard coded as newuser the password is the PW defined in the .KEYCLOAK.info file from earlier (that should have been deleted after noting down what the password was ;))


Keycloak Realms


I am no Keycloak expert and the realm approach is open to interpretation on how to set these up for any use case. So my setup is a home lab with few users, therefore I decided on a dedicated realm for each use case - it is obviously best to check out realm usage for any production based projects.


I used a naming convention to identify which Keycloak service/function was for each by noting -an- for Ansible and -tr- for Terraform. E.g. auto-an-realm is for the public based client defined in Keycloak for the Ansible. All the configuration is setup automatically via the kcadm.sh cli within the startup scripts for the Keycloak container. There is no manual intervention required to run the test Hello World web apps. Check out the scripts for these kcadm.sh commands and flow.


Screenshot shows the defined realms that are all created when starting the project for the first time. (There is a private client realm and client defined but not currently used).








Miscellaneous actions


Remove demo certs from certstore, from the example code only - if the root CA name is changed then the *basestep* match won't match any…..


Get-ChildItem -Path Cert:\CurrentUser\Root | Where-Object { $_.Subject -like '*basestep*' } | ForEach-Object {

    certutil -user -delstore Root $_.Thumbprint

}





No comments:

Post a Comment

Google +