<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RESTful API on Digi Hunch</title><link>https://www.digihunch.com/tag/restful-api/</link><description>Recent content in RESTful API on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Sat, 20 Jul 2024 21:41:02 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/restful-api/index.xml" rel="self" type="application/rss+xml"/><item><title>Host legacy application in Docker 1 of 2</title><link>https://www.digihunch.com/2020/09/host-legacy-application-with-docker-compose/</link><pubDate>Fri, 04 Sep 2020 16:24:00 -0400</pubDate><guid>https://www.digihunch.com/2020/09/host-legacy-application-with-docker-compose/</guid><description>&lt;p class="wp-block-paragraph"&gt;This is my notes from containerizing a legacy application with Docker &lt;a href="https://www.digihunch.com/2020/05/docker-swarm-brief-notes/"&gt;compose&lt;/a&gt;. We have to run multiple instances of our application because we&amp;#8217;re unable to secure additional VMs for this single-VM education environment. The application is target of containerization, because it requires mass reconfiguration (around TCP port) to run multiple instances of the application. We want to use the same application configuration file for multiple containers, and map the TCP port to different groups of ports on the host, leveraging port mapping in Docker. On the other hand, the auxiliary services are not being containerized, such as Cassandra database and ElasticSearch because they can be shared for multiple application instances. In other words, we use Docker to isolate processes of the same application.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-prepare-environment"&gt;Prepare environment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CentOS server needs to have docker-ce (through YUM) as well as docker-compose (direct download). They can be installed this way:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo yum install docker-ce docker-ce-cli containerd.io&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -L &lt;span style="color:#e6db74"&gt;&amp;#34;https://github.com/docker/compose/releases/latest/download/docker-compose-&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;uname -s&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;uname -m&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -o /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo chmod +x /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo systemctl start docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Our Docker registry is not publicly available. So we need to port the Docker image we need to remote server and load it into the local registry. We first examine the registry locally:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -XGET https://admin:password@docker.digihunch.com/v2/dhunch/tags/list | python -m json.tool&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we identify the image, we export it to a tar file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker save docker.digihunch.com/dhunch &amp;gt; dhunch_image.tar&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;SCP the file to remote server and load it locally:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker load -i /home/dhunch/dhunch_image.tar&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker image ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We need to distinguish these commands:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;docker save&lt;/strong&gt;: saves an (non-running) image with all layers to file&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker export&lt;/strong&gt;: saves a running or paused container to file&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker import&lt;/strong&gt;: import the contents from a tarball to create a filesystem image, most used with docker export&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker load&lt;/strong&gt;: load an image from a tar archive or STDIN, most used with docker save&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-build-docker-compose-file"&gt;Build docker-compose file&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I need to cater to the customer environment with a newly create docker-compose file. The customer environment includes specific storage and networking configurations. Docker compose&amp;#8217;s official documentation is &lt;a href="https://docs.docker.com/compose/compose-file/"&gt;here&lt;/a&gt;. We repeat the following commands for our troubleshooting:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker-compose exec -it dhunch1 bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker container ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we start the container, the status might go unhealthy after it starts. The documentation explains two reasons you&amp;#8217;re seeing an unhealthy container:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;a single run of the command takes longer than the specified timeout&lt;/li&gt;&#10;&lt;li&gt;health check fails; the health check command will retry a number of times before it declares the container as unhealthy.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In our case,&amp;nbsp; It is most likely because it does not pass a built-in health check mechanism. We need to understand where the health check was defined. There are four ways to enable health check:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Dockerfile instruction when building the image&lt;/li&gt;&#10;&lt;li&gt;Docker run command&lt;/li&gt;&#10;&lt;li&gt;Docker-compose or docker stack yaml file&lt;/li&gt;&#10;&lt;li&gt;Docker service&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With #1, unfortunately, you can&amp;#8217;t reverse engineer an image and view the Dockerfile that were used to built it and review the health check statement. What you can do is check docker events, or inspect the container, and go to the log files as specified under logPath section in the inspection result and look for HealthCheck section. We determined it is the case, then we can disable, or override the built-in healthcheck command from image, with a statement in docker compose.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For network interface, docker compose also&amp;nbsp;allows us to specify MAC address for each container with mac_address keyword (for license key). MAC address generator are available on the internet. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-entrypoint-vs-cmd"&gt;EntryPoint vs CMD&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The difference between EntryPoint and CMD is very important when launching container. Some literature also mentions RUN, which is only used when &lt;span style="text-decoration: underline;"&gt;building a new layer of images&lt;/span&gt; so it is not relevant here (in the context of launching a container from image). EntryPoint and CMD has similar functionalities both allowing you to specify a command to run. The &lt;span style="text-decoration: underline;"&gt;difference is whether they can be overwritten by command line arguments&lt;/span&gt; that user provide to docker-compose or docker run in an ad-hoc manner. As their names suggests, EntryPOINT means what is specified under it must be executed as it launches into the container, regardless of any adhoc commands. On the other hand, CMD is just an entry to save users from typing in a command every time they run docker compose or docker run. Should user prefer a different command, it can be provided as an explicit argument and it will be respected overwriting the pre-defined CMDentry in Dockerfile or command entry in docker-compose.yml.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both CMD and EntryPoint supports shell and exec forms. More details &lt;a href="https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-choice-of-networking"&gt;Choice of Networking&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With single-host deployment, the containerized application needs to communicate with other existing, non-containerized service on host, such as database or elastic search. If docker uses host network, the container shares interface with the host and it does not have its own IP address. Host network removes isolation between container and host. This allows container to run the application that was licensed to the host based on MAC address. There is also no port mapping from container to host network. Container simply uses port on host, and is subject to the availability of TCP/UDP port on host.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will have to use bridge network here. We can force MAC address the app container, and pre-generate license. For container to &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;communicate with a service on host&lt;/a&gt;, through bridge network, there are two problems to address:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Container knows the IP of the host (layer-3 connectivity, ping);&lt;/li&gt;&#10;&lt;li&gt;Making host service available to container (layer-4 connectivity, telnet);&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker creates its own interface for bridge network. If it&amp;#8217;s an unnamed network, i.e. not explicitly declared under networks section in docker compose, then interface docker0 is used. If it&amp;#8217;s a named network, then an interface name starting with br- is used.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first problem is easier to address, we simply needs to IP address of the host on the interface. We can validate by pinging from container to host. Docker can also use &lt;strong&gt;host.docker.internal &lt;/strong&gt;to reference the host. Unfortunately, this &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;stopped working for linux&lt;/a&gt; since 18.09.3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is reportedly to be fixed in 20.04 and until it is available, we may add it to manual dns. The following command outputs the entry to add to /etc/hosts in container.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ip -4 addr show $(basename -a /sys/class/net/* | grep ^br-) | grep -Po &amp;#39;inet \K[\d.]+&amp;#39; | awk &amp;#39;{print $1 &amp;#34; host.docker.internal&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To do this automatically in docker compose, we need some tricks:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Store the Host IP in host environment variable ( use an export command)&lt;/li&gt;&#10;&lt;li&gt;Use compose to pass host environment variable to container environment variable&lt;/li&gt;&#10;&lt;li&gt;Have the container write its environment variable to /etc/hosts&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The compose file will contain a line like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; myenv1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: alpine&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; command: &amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;apk update &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &lt;/span&gt;$$&lt;span style="color:#e6db74"&gt;HostDNSLine &amp;gt;&amp;gt; /etc/hosts &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; bash&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#network_mode: bridge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - HostDNSLine&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;HOSTDNSREC&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note ampersand might be mistakenly displayed as &amp;amp;amp; in the above. Then we run it with the following:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# export HOSTDNSREC=$(echo 1.2.3.4 host.docker.internal) &amp;amp;&amp;amp; docker-compose up&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The second problem is harder to address because the service on host may not bind to docker&amp;#8217;s interface. Some services such as ssh bind to all interfaces on host and you can telnet to port 22 with any IP address the host is associated with. This is however not the case for most other services, such as Cassandra or Elastic Search. They typically only bind to main interface, such as ens192, or eth0, and not to the docker interface. In order to make the service available to container, we either need to bind these services to the docker interface, or use iptables rules as an alternative.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose it is a named network and Docker&amp;#8217;s interface name is br-90ae024d5324, and the service on host listens to port 9042, we will need&amp;nbsp; the following two commands from host:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# sysctl -w net.ipv4.conf.br-90ae024d5324.route_localnet=1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# iptables -t nat -A PREROUTING -p tcp -i br-90ae024d5324 --dport 9042 -j DNAT --to-destination 127.0.0.1:9042&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that docker compose can configure to run sysctl in container but not from host. If there are multiple ports, we can turn this into a shell script:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tcp_port_list&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9200 9042 8302 8303 8304 8305 8306&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;basename -a /sys/class/net/* | grep ^br- | head -1&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo enable route localnet on interface $if_name&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sysctl -w net.ipv4.conf.$if_name.route_localnet&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tcp_port in $tcp_port_list; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo open host tcp port $tcp_port to interface $if_name&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iptables -t nat -A PREROUTING -p tcp -i $if_name --dport $tcp_port -j DNAT --to-destination 127.0.0.1:$tcp_port&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ip -4 addr show &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;basename -a /sys/class/net/* | grep ^br-&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; | grep -Po &lt;span style="color:#e6db74"&gt;&amp;#39;inet \K[\d.]+&amp;#39;&lt;/span&gt; | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{print $1 &amp;#34; host.docker.internal&amp;#34;}&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;On the other hand, binding service to multiple interfaces usually require some re-configuration on the service itself. For example, if it is Elastic Search, we need to update [network.host] entry in elasticsearch.yml to include multiple IP addresses. For Cassandra, we need to update rpc_address to 0.0.0.0 or set rpc_interface in &lt;a href="https://docs.datastax.com/en/developer/java-driver/3.0/manual/address_resolution/"&gt;cassandra.yml&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-integration-with-storage"&gt;Integration with storage&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The application in the container need to store files to storage available to host, whether it is an NFS share or a block disk. We can use volume mapping with Docker compose, to map a path in container to a path presented to host as persistent volume. At this step, we might run into permission issues. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, containers initializes as root (uid=1) within the container, and the entrypoint script launches application as root. When application writes to persistent volume, files are written as root user. In the legacy non-container setup, we expect the application to write file as dhunch user. Moreover, NFS volume will not allow writing files as root (if the server has &lt;a href="https://en.wikipedia.org/wiki/Unix_security#Root_squash"&gt;root squash&lt;/a&gt; configured). To address this, there are two approaches:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;launch container as a regular user&lt;/li&gt;&#10;&lt;li&gt;launch container as root user, then have the entrypoint script launch application as regular user (dhunch)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For approach 1, we need to tell Docker to launch container as a regular user by specify the uid and gid for container to run application. We can specify the following envrionment variable in the compose yaml:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user: &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;CURRENT_UID&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we assign the environment variable before running docker-compose:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# export CURRENT_UID=$(id -u dhunch):$(id -g dhunch) &amp;amp;amp;&amp;amp;amp; docker-compose up&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This allows container to initialize as the regular user. However, if the entry point script needs to perform activities that requires root permission within the container, it will fail. For example, a regular user in container will not be able to update /etc/hosts;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With approach 2, we do not specify user in docker compose so container initializes as root. Then the entry point script launches application as regular user. For example, use su command before launch Java:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;su dhunch -c &lt;span style="color:#e6db74"&gt;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;exec java \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -Xms512M -Xmx8192M \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -Djava.io.tmpdir=&lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/var/tmp \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -server \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -XX:CompileCommandFile=&lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/etc/hotspot_compiler \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -jar &lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/lib/jar/jruby-complete-*.jar \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --1.9 \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/lib/rubybin/runapp.rb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Before doing this, we need to first create user dhunch within container, and the uid and gid must match those of the host. So that when container picks up dhunch user, it converts it to the correct uid.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;groupadd -g &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;useradd -m -c &lt;span style="color:#e6db74"&gt;&amp;#39;regular user&amp;#39;&lt;/span&gt; -u &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; -g &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To further understand how uid and gid work,&lt;a href="https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf"&gt; here&lt;/a&gt; are &lt;a href="https://medium.com/redbubble/running-a-docker-container-as-a-non-root-user-7d2e00f8ee15"&gt;two&lt;/a&gt; posts with more information.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This user ownership setup will also work for NFS. To configure NFS, we need some extra client-side configurations in the container, as well as a special volume driver for NFS. Refer to &lt;a href="https://stackoverflow.com/questions/45282608/how-to-directly-mount-nfs-share-volume-in-container-using-docker-compose-v3"&gt;this&lt;/a&gt; post.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/08/zookeeper/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Zookeeper Summary&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/09/intro-to-big-data-projects/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Big Data Projects&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>OAuth 2.0 and OIDC 1 of 2</title><link>https://www.digihunch.com/2020/03/oauth-and-openid-connect/</link><pubDate>Sat, 14 Mar 2020 21:10:00 -0400</pubDate><guid>https://www.digihunch.com/2020/03/oauth-and-openid-connect/</guid><description>&lt;p class="wp-block-paragraph"&gt;OAuth 2.0 and OpenID Connect (OIDC 1.0) are different but highly related protocols and they are often confused. When we talk about IAM (identity and access management), we should first distinguish between Authentication (AuthN) and Authorization (AuthZ):&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;AutheNtication (AuthN, aka Identity Management) is about validating user&amp;#8217;s identity by verifying that the user trying to connect is actually who it claims itself to be;&lt;/li&gt;&#10;&lt;li&gt;AuthoriZation (AuthZ, aka Access Management) refers to granting or denying access to specific resources based on the requesting user&amp;#8217;s identity. It is usually performed after a user is identified through authentication. The most common approach is Role-Based Access Control (RBAC).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a nutshell, OAuth 2.0 deals with authorization. OIDC is a layer later developed on top of OAuth 2.0, to deal with authentication. This post is greatly influenced by a &lt;a href="https://www.youtube.com/watch?v=996OiexHze0"&gt;presentation&lt;/a&gt; (1 hour) delivered by Nate Barbettini from Okta, with the slides available &lt;a href="https://speakerdeck.com/nbarbettini/oauth-and-openid-connect-in-plain-english"&gt;here&lt;/a&gt;. There is also an abridged and illustrated video (16 min) by Okta available &lt;a href="https://www.youtube.com/watch?v=t18YB3xDfXI"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="oauth-2-0"&gt;OAuth 2.0&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OAuth was originally developed by Twitter and Google in 2006 as an open standard for API authorization. &lt;strong&gt;OAuth 2.0&lt;/strong&gt; is published in 2012. It allows user to &lt;span style="text-decoration: underline;"&gt;delegate authorization&lt;/span&gt;. The original scenario is a user signed up to a new application and allows it to automatically import her Gmail contact. The technical problem to solve is: how can a user (Resource Owner) let an app (Client) to access his contact list stored in Google server (Resource Server)? The proposal is that it redirects user to Google Account page (Authorization Server) for user to log in. Then Google Account issues token to the application (Client) with user&amp;#8217;s approval. Note that the user did NOT log in to the application itself with her Google account. From the application&amp;#8217;s standpoint, the user had been authenticated already, and was simply importing contact after logging in. The roles involved in OAuth 2.0 are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Resource: the contact list of the user&lt;/li&gt;&#10;&lt;li&gt;Resource owner: the user&lt;/li&gt;&#10;&lt;li&gt;Client: the application&lt;/li&gt;&#10;&lt;li&gt;Resource Server: contact.google.com&lt;/li&gt;&#10;&lt;li&gt;Authorization Server: accounts.google.com&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The diagram below illustrates the interactions:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://assets.digitalocean.com/articles/oauth/auth_code_flow.png" alt="Authorization Code Flow"/&gt;&lt;figcaption class="wp-element-caption"&gt;OAuth 2.0 Authorization Code Flow&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2"&gt;This&lt;/a&gt; page has further details for each step. Note that at step 3 to 5 may seem unnecessary because Auth Server could have send Access Token Grant to Application via User-Agent at step 3, which could have eliminate the need for step 4 and 5. In fact this design is to avoid sending critical information (Access Token Grant) to User-Agent (browser) which is considered in secure. In other words, it avoids front channel (User-agent to auth server) and prefers back channel (Client to Auth server) for security. This is the difference between Authorization code flow and the implicit flow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With OAuth 2.0, there are a number of flows:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Authorization code (front channel and back channel)&lt;/li&gt;&#10;&lt;li&gt;Implicit (front channel only, token returned to user agent directly)&lt;/li&gt;&#10;&lt;li&gt;Resource owner password credentials (back channel only)&lt;/li&gt;&#10;&lt;li&gt;Client credentials (back channel only)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Authorization Code Flow, the Application (client) needs a one-time registration with the Auth Server and is given a client ID and client secret, which are sent to Auth Server at step 4 along with Access Token Request, to prove the identity of the client application.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that OAuth 2.0 is an inherently insecure protocol since it does not support signature, encryption, channel binding or client verification. The protocol relies entirely on the underlying transport layer security (TLS) to provide confidentiality and integrity.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Also note that throughout the process (Authorization Code Flow as an example), the Client application eventually is granted access to user&amp;#8217;s data. However, it does not know anything about the user itself. Neither the authorization code grant, nor the access token grant is &lt;span style="text-decoration: underline;"&gt;obligated&lt;/span&gt; to present information about the user itself. Therefore, OAuth 2.0 is designed strictly for permission purpose without the intent to address identity issue. In the flow, the auth server does the authentication (for the purpose of granting access to resources, but none of the authentication. A user logs in to client application as Bob, when he requests to imports contact, he is redirected to account.google.com and there he could put in the credential of Alice and therefore load Alice&amp;#8217;s Google contacts into Bob&amp;#8217;s App account!&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="pseudo-authentication-with-oauth-2-0"&gt;Pseudo-authentication with OAuth 2.0&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In many real life OAuth 2.0 implementations, at step 3, the Auth server chooses to include a field about the user&amp;#8217;s identity. This makes user&amp;#8217;s identity visible to the client, and the client is therefore able to confirm user&amp;#8217;s identity in its own code. This also allows client application to use OAuth 2.0 as an authentication method, which is referred to as pseudo-authentication. The access token acts as a kind of &amp;#8220;valet key&amp;#8221; that the application can include with its request to the auth server, as a proof that it has user&amp;#8217;s permission to access the resources (or APIs).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because the identity provider (auth server) typically (but not always) authenticates the user as part of the process of granting an OAuth access token, it&amp;#8217;s tempting to view a successful OAuth access token request as an authentication method itself. However, because OAuth was not designed with this use case in mind, making this assumption can lead to major security flaws.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Nate&amp;#8217;s presentation outlined some scenarios where OAuth 2.0 is applied up to 2012, and which ones are misuses:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Simple login &amp;#8211; pseudo authentication with OAuth 2.0&lt;/li&gt;&#10;&lt;li&gt;Single sign-on across sites &amp;#8211; pseudo authentication with OAuth 2.0&lt;/li&gt;&#10;&lt;li&gt;Mobile app login &amp;#8211; pseudo authentication with OAuth 2.0&lt;/li&gt;&#10;&lt;li&gt;Delegated authorization &amp;#8211; the only intended use case for OAuth 2.0&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To address the authentication issue properly, and in a standard approach, we need OpenID Connect.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="openid-connect-oidc"&gt;OpenID Connect (OIDC) &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenID Connect is an open standard for authentication, promoted by the non-profit OpenID Foundation. It allows user to be authenticated using a third-party service called identity providers. User may choose to use their preferred OpenID Connect providers to log in to websites that accept the OpenID Connect authentication scheme. For example, a user uses her Facebook to login to an online application.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenID Connect is an extension to OAuth 2.0 with a just few additions:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;In addition to access token, an ID token is returned by the authorization server;&lt;/li&gt;&#10;&lt;li&gt;Userinfo end point is provided in case Id token is not sufficient and more user information is needed;&lt;/li&gt;&#10;&lt;li&gt;&amp;#8220;openid&amp;#8221; is passed as a parameter in the Scope during the initial call to the authorization server;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Therefore OpenID Connect is considered an identity layer on top of OAuth 2.0. Many application supports OpenID Connect such as &lt;a href="https://nifi.apache.org/"&gt;Apache Nifi&lt;/a&gt;. OIDC is comparable with SAML in the sense that both provide SSO feature (federated identity). Here is a comparison table:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&#10;&lt;table id="tablepress-6" class="tablepress tablepress-id-6"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;td class="column-1"&gt;&lt;/td&gt;&lt;th class="column-2"&gt;OpenID Connect&lt;/th&gt;&lt;th class="column-3"&gt;SAML&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td class="column-1"&gt;Main Purpose&lt;/td&gt;&lt;td class="column-2"&gt;SSO for consumer/mobile applications&lt;/td&gt;&lt;td class="column-3"&gt;SSO for enterprise applications&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-1"&gt;Load&lt;/td&gt;&lt;td class="column-2"&gt;Relatively light weight&lt;/td&gt;&lt;td class="column-3"&gt;Heavy weight due to the size of XML messages &lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-1"&gt;Use case&lt;/td&gt;&lt;td class="column-2"&gt;Satisfies both authentication and authorization use cases, often combined with OAuth 2.0&lt;/td&gt;&lt;td class="column-3"&gt;Generally not used for API security&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-1"&gt;Transport&lt;/td&gt;&lt;td class="column-2"&gt;HTTP GET and HTTP POST&lt;/td&gt;&lt;td class="column-3"&gt;HTTP Redirect (GET) binding, SAML SOAP binding, HTTP POST binding, et&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-1"&gt;&lt;/td&gt;&lt;td class="column-2"&gt;&lt;/td&gt;&lt;td class="column-3"&gt;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://medium.com/@awskarthik82/simple-guide-to-saml-vs-oidc-33a3349189c6"&gt;Here &lt;/a&gt;are more details about their differences. In general SAML is more common in the enterprise world for SSO and it has been around for a while. When developing new applications for enterprise it is advised to consider OIDC first.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OIDC also has authorization code flow, with the additional fields on top of its counterpart in OAuth 2.0. The authorization server returns both access and ID tokens, wrapped in a data structure named JWT (JSON Web Token). The JWT includes a signature field, allowing the client application to verify it with authorization server&amp;#8217;s public key. Nate&amp;#8217;s presentation proposes the following flows for each application type:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Web application with server backend: authorization code flow&lt;/li&gt;&#10;&lt;li&gt;Native mobile app: authorization code flow with PKCE&lt;/li&gt;&#10;&lt;li&gt;Java Script app (SPA) with API backend: implicit flow&lt;/li&gt;&#10;&lt;li&gt;Microservices and APIs: client credential flow&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition, &lt;a href="https://developer.okta.com/docs/concepts/oauth-openid/"&gt;this&lt;/a&gt; page from Okta developer has a good summary of how to select flow type (grant) based on each use case.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenID Connect is an authentication protocol for the purpose of validating user&amp;#8217;s identity. OAuth 2.0 is an authorization protocol. You should use OAuth 2.0 for granting access to your API, or access to user data in other systems. If you need to log user in, or make your accounts available in other systems, you need OIDC.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://www.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;next post&lt;/a&gt; about OAuth and OIDC was posted in 2023.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;br&gt; &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/03/saml-security-assertion-markup-language/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Security Assertion Markup Language (SAML)&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/03/authentication-mechanisms-under-simple-authentication-and-security-layer-sasl/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;SASL Authentication Mechanisms&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>