<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julian</title>
    <description>The latest articles on DEV Community by Julian (@julianalmanzar).</description>
    <link>https://dev.to/julianalmanzar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1146347%2F8458959d-89f8-4bd4-b481-475356dbeed5.jpeg</url>
      <title>DEV Community: Julian</title>
      <link>https://dev.to/julianalmanzar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/julianalmanzar"/>
    <language>en</language>
    <item>
      <title>Backup Web Application + Database to AWS S3</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Thu, 12 Oct 2023 16:12:38 +0000</pubDate>
      <link>https://dev.to/julianalmanzar/backup-web-application-database-to-aws-s3-1mpj</link>
      <guid>https://dev.to/julianalmanzar/backup-web-application-database-to-aws-s3-1mpj</guid>
      <description>&lt;p&gt;Not everybody, for a lot of administrative, legal or political reasons, can run applications in a cloud infrastructure, nor has any suitable software available to fulfill the needs that may arise, and when that happens, us system administrators need to get creative and design our own scripts to deal with our needs.&lt;/p&gt;

&lt;p&gt;This need in particular I'll be addressing here is &lt;strong&gt;backup for applications and databases&lt;/strong&gt;. In a lot of places, for lots of different reasons, you can't run applications on the cloud, but you can have backups stored in the cloud or somewhere else. To do this, I created a simple script that will save everything we need to save to run our application and make it possible to restore just as it was to and from the cloud.&lt;/p&gt;

&lt;p&gt;In this example, we're going to backup a simple LAMP application that uses a database to save and list information to AWS S3. The goal is to save the source code and the database automatically in a way that can be easily retrieved and installed in a new server and run as if nothing else happened.&lt;/p&gt;

&lt;p&gt;To make this script work, we'll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path(s) for the application files&lt;/li&gt;
&lt;li&gt;Database(s) name and Database Server(s) IP(s) and credentials&lt;/li&gt;
&lt;li&gt;Use the same credentials across all of the database servers&lt;/li&gt;
&lt;li&gt;AWS CLI installed and configured&lt;/li&gt;
&lt;li&gt;AWS S3 destination bucket already created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Take in consideration that you'll probably need to update the source code after a restore because the database server IP and/or credentials may change. This script is designed to be ran in the server that runs the applications.&lt;/p&gt;

&lt;p&gt;To create the backup, we'll use the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

#Initializing our variables
#db_username, db_passwd and aws_bucket are not suposed to be used as arrays
paths=()
ips=()
databases=()
db_username=()
db_passwd=()
aws_bucket=()
#Getting our parameters and creating our lists
while [[ $# -gt 0 ]]; do
    case "$1" in
        -p)
            paths+=("$2")
            shift 2;;
        -i)
            ips+=("$2")
            shift 2;;
        -n)
            databases+=("$2")
            shift 2;;
        -u)
            db_username+=("$2")
            shift 2;;
        -w)
            db_passwd+=("$2")
            shift 2;;
        -b)
            aws_bucket+=("$2")
            shift 2;;

        *)
            echo "Unknown flag: $1"
            exit 1;;
    esac
done

#Create a directory to save current backup
mkdir backup$(date +"%d%m%Y")
#Create a log file
touch backup$(date +"%d%m%Y")/logs.txt
#Iterate through all paths and create a backup for each one of them
for path in "${paths[@]}"; do
    tar -cvpzf backup$(date +"%d%m%Y")/$(basename $path).tar.gz $path
    echo "Backup for $path saved as $(basename $path).tar.gz" &amp;gt;&amp;gt; backup$(date +"%d%m%Y")/logs.txt
done
##Iterate through all database servers IPs and check if the database name exists and create a backup
for ip in "${ips[@]}"; do
    for database in "${databases[@]}"; do
        if mysql -u "$db_username" -p"$db_passwd" -h "$ip" -e "USE $database;" 2&amp;gt;/dev/null; then
            sudo mysqldump --no-tablespaces -u $db_username -p$db_passwd "$database" &amp;gt; backup$(date +"%d%m%Y")/"$database$ip.sql"
            echo "Database $database from $ip saved as $database$ip.sql" &amp;gt;&amp;gt; backup$(date +"%d%m%Y")/logs.txt
        else
            echo "Database $database doesnt exists on $ip server" &amp;gt;&amp;gt; backup$(date +"%d%m%Y")/logs.txt
        fi
    done
done

#Create a tar for the whole folder
tar -cvpzf backup$(date +"%d%m%Y").tar.gz backup$(date +"%d%m%Y")
#Upload file to AWS bucket
aws s3 cp backup$(date +"%d%m%Y").tar.gz s3://$aws_bucket/
echo "File backup$(date +"%d%m%Y").tar.gz uploaded to $aws_bucket" &amp;gt;&amp;gt; backup$(date +"%d%m%Y")/logs.txt
#Delete local folder and file
rm -rf backup$(date +"%d%m%Y")
rm backup$(date +"%d%m%Y").tar.gz

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we're basically saving all of the files listed in the different paths passed in a tar.gz file, then backing up the databases passed as parameters in the list and then, creating a tar of all of the created files and deleting the remaining files. After that, the tar file is uploaded to an S3 bucket and deleted from the disk to save space. We're also logging all of the paths and the IP of the database servers where databases are being backed up from so you know where it was before restoring and take certain action if needed.&lt;/p&gt;

&lt;p&gt;This script can be executed as frequently as needed, taking in consideration the costs of transfer and the size of the files.&lt;/p&gt;

&lt;p&gt;Alternatively, you can backup your files to a different destination like an FTP server for example.&lt;/p&gt;

&lt;p&gt;You can find about the usage to this script at my &lt;a href="https://github.com/julianalmanzar/aws_backup"&gt;GitHub&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;I hope this helps someone out to backup applications and databases in a simple but effective way.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>aws</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Network Automation with Python using network devices' REST API Interface</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sat, 16 Sep 2023 14:06:44 +0000</pubDate>
      <link>https://dev.to/julianalmanzar/network-automation-with-python-using-network-devices-rest-api-interface-4j1c</link>
      <guid>https://dev.to/julianalmanzar/network-automation-with-python-using-network-devices-rest-api-interface-4j1c</guid>
      <description>&lt;p&gt;In this tutorial I'll show you how to configure network devices using their integrated API interface. To understand this, you should be familiar with Python, REST APIs and networking because this is intended to show how to integrate those things together. &lt;/p&gt;

&lt;p&gt;The network I configured with this script looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnv21eiz8jj2daniqymcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnv21eiz8jj2daniqymcn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the API interface instead of the CLI is not just faster when applying configurations on multiple devices, it allows us to take advantage of the programming language's functions, creating more flexible and smarter configurations and apply them on our network devices as needed. For this, we're going to use the &lt;a href="https://requests.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;requests&lt;/a&gt; and &lt;a href="https://urllib3.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;urllib3&lt;/a&gt; libraries from Python.&lt;/p&gt;

&lt;p&gt;You can also configure lots of devices at the same time and modify the applied configurations using conditions such as a name, a serial number ... literally anything you want to. There are tons of possibilities, it will depend on your creativity, network understanding and programming skills.&lt;/p&gt;

&lt;p&gt;The example I'm going to show here is a Python script that creates and applies all of the base configurations on our network devices, in my case, &lt;strong&gt;ArubaOS CX switches&lt;/strong&gt;. I developed this script to configure everything on the access switches newly installed in a network. The only things that you need to use this script are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote management (https) enabled and reachable from you're executing the script&lt;/li&gt;
&lt;li&gt;Device's credentials&lt;/li&gt;
&lt;li&gt;The Uplink interface of your devices (when I install network devices I use the 1st available fiber port for example, you just need to have them identified)&lt;/li&gt;
&lt;li&gt;Ports distribution depending on the network requirements&lt;/li&gt;
&lt;li&gt;Understanding of the API interface of the target network devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First of all, I listed all of the basic parameters I  wanted to configure on my network devices. These are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Device description&lt;/li&gt;
&lt;li&gt;DNS, NTP, Syslog, DHCP Snooping and Radius (for centralized management) settings&lt;/li&gt;
&lt;li&gt;VLANS and Voice VLAN&lt;/li&gt;
&lt;li&gt;Port distribution (Trunk, access etc...) depending on the needs of the network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note: Your needs can be different to my network's, so pay attention to the API documentation of your device for the parameters you need so you can find the correct destination of the requests later.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After that, I started looking for what I needed to configure on my devices in the ArubaOS CX API interface documentation. In my case, my targets where:&lt;/p&gt;

&lt;p&gt;Hostname, timezone and DHCP Snooping -&amp;gt; /rest/v10.12/system&lt;br&gt;
DNS Server and Domain Name -&amp;gt; /rest/v10.12/system/vrfs/default&lt;br&gt;
NTP Server -&amp;gt; /rest/v10.12/system/vrfs/default/ntp_associations&lt;br&gt;
Syslog Server -&amp;gt; /rest/v10.12/system/syslog_remotes&lt;br&gt;
Radius Dynamic Authentication Clients -&amp;gt; /rest/v10.12/system/vrfs/default/radius_dynamic_authorization_clients&lt;br&gt;
AAA Groups -&amp;gt; /rest/v10.12/system/aaa_server_groups&lt;br&gt;
Radius Servers -&amp;gt; /rest/v10.12/system/vrfs/default/radius_servers&lt;br&gt;
SSH Login Priority -&amp;gt; /rest/v10.12/system/aaa_server_group_prios/ssh&lt;br&gt;
HTTPS Login priority -&amp;gt; /rest/v10.12/system/aaa_server_group_prios/https-server&lt;br&gt;
VLANS -&amp;gt; /rest/v10.12/system/vlans/&lt;br&gt;
Voice VLAN -&amp;gt; /rest/v10.12/system/vlans/{voice-vlan-id}&lt;br&gt;
Ports -&amp;gt; /rest/v10.12/system/interfaces/{port}&lt;/p&gt;

&lt;p&gt;After having my targets, I build the json structure of the data to send based on the API documentation and started to build the code as reusable as possible (I believe it has a lot of improvement opportunities) using this structure:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Dictionary that contains all network devices to be configured
# Along with the hostname that will be configured on each of them
switches = {"10.10.10.50": "arubaoscxsw01"}

# Credentials for the network devices, this case assumes all credentials are the same
# on all network devices
credentials = {"username": "admin", "password": "123456"}

# This is the base config dictionary, modify this as needed
# This could perfectly be a list of parameters so we can use this script faster in real implementations
base_config = {"dns_servers": ["1.1.1.1", "8.8.8.8"],
               "dns_domain_name": "domain.local",
               "ntp_server": "time.google1.com",
               "timezone": "UTC",
               "syslog_server": {"address": "syslogserver.local", "severity": "err"},
               "radius_server": {"group": "radiusgroup", "address": "radiusserver.local", "key": "serverkey"},
               "vlans": {9: "Users", 10: "SRV", 20: "VoIP", 50: "WiFi"},
               "voice_vlan": 20,
               "uplink_description": "UPLINK",
               "uplink_ports": ["1/1/1"],
               "wap_ports": ["1/1/2", "1/1/3"],
               "access_ports": ["1/1/4", "1/1/5", "1/1/6"],
               "server_ports": ["1/1/7"],
               "authorized_dhcp_servers": ["10.0.10.9"]
               }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This part has the basic information to establish the connection with the switches and the parameters we want to configure. This is where the JSON data is going to be filled with as shown here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Here we build the JSON configs from the base_config
# Consult the API to know how to build the config and
# where to POST/PATCH your desired configuration
# In this case, dictionaries match the JSON structure of the ArubaOS CX 10.12 API
dns_dhcp_auth_srv_config = {"dns_domain_name": base_config["dns_domain_name"],
              "dns_name_servers": {str(index): value for index, value in enumerate(base_config["dns_servers"])},
              "dhcpv4_snooping_authorized_servers": base_config["authorized_dhcp_servers"]}

ntp_server = {"address": base_config["ntp_server"],
              "association_attributes": {
                  "burst_mode": "iburst",
                  "maxpoll": 10,
                  "minpoll": 6,
                  "ntp_version": 4,
                  "prefer": False,
                  "ref_clock_id": "--"
              },
              "key_id": None,
              "origin": "configuration",
              "vrf": "/rest/v10.12/system/vrfs/default"}

syslog_server = {"remote_host": base_config["syslog_server"]["address"],
                 "severity": base_config["syslog_server"]["severity"],
                 "tls_auth_mode": "certificate",
                 "transport": "udp",
                 "unsecure_tls_renegotiation": False,
                 "vrf": {
                     "default": "/rest/v10.12/system/vrfs/default"}}

radius_dyn_auth_client = {
    "address": base_config["radius_server"]["address"],
    "connection_type": "udp",
    "replay_protection_enable": True,
    "secret_key": base_config["radius_server"]["key"],
    "time_window": 300,
    "vrf": "/rest/v10.12/system/vrfs/default"}


radius_server_groups = {"group_name": base_config["radius_server"]["group"],
                        "group_type": "radius",
                        "origin": "configuration"}

radius_server = {"accounting_udp_port": 1813,
                 "address": base_config["radius_server"]["address"],
                 "auth_type": "pap",
                 "passkey": base_config["radius_server"]["key"],
                 "port": 1812,
                 "port_access": "status-server",
                 "port_type": "udp",
                 "retries": None,
                 "server_group": {
                     f"/rest/v10.12/system/aaa_server_groups/{base_config['radius_server']['group']}": 1
                 },
                 "timeout": None,
                 "vrf": "/rest/v10.12/system/vrfs/default"}

aaa_prio_ssh_https = {"authentication_group_prios":
                    {"0": f"/rest/v10.12/system/aaa_server_groups/{base_config['radius_server']['group']}"}
                }
vlan_config = {"id": None, "name": None
               }
voice_vlan = {"voice": True}

access_port_config = {"vlan_mode": "native-untagged",
                      "vlan_tag": {
                          "9": "/rest/v10.12/system/vlans/9"
                      },
                      "vlan_trunks": {
                          "9": "/rest/v10.12/system/vlans/9",
                          "20": "/rest/v10.12/system/vlans/20"},
                      "admin": "up"
                      }

server_port_config = {"vlan_mode": "access",
                      "vlan_tag": {
                          "10": "/rest/v10.12/system/vlans/10"},
                      "admin": "up"
                      }

wap_port_config = {"vlan_mode": "native-untagged",
                      "vlan_tag": {
                          "50": "/rest/v10.12/system/vlans/50"
                      },
                      "vlan_trunks": {
                          "9": "/rest/v10.12/system/vlans/9",
                          "50": "/rest/v10.12/system/vlans/50"},
                   "admin": "up"
                   }

uplink_port_config = {"vlan_mode": "native-untagged",
                      "vlan_tag": None,
                      "vlan_trunks": {
                          "1": "/rest/v10.12/system/vlans/1",
                          "9": "/rest/v10.12/system/vlans/9",
                          "10": "/rest/v10.12/system/vlans/10",
                          "20": "/rest/v10.12/system/vlans/20",
                          "50": "/rest/v10.12/system/vlans/50"},
                      "dhcpv4_snooping_configuration": {
                          "trusted": "true"},
                      "description": base_config["uplink_description"],
                      "admin": "up"
                      }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, the structure is filled with the information on the &lt;code&gt;base_config&lt;/code&gt; dictionary.&lt;/p&gt;

&lt;p&gt;After having all of our data organized, we can start posting/patching data as needed on our destinations like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# We create our request session
sesion = requests.Session()

# We loop for every network device in our dictionary and apply configuration as
# the API says
for switch in switches:
    # This part substitutes the hostname in every network device
    # iteration in the dictionary for later use it to apply it
    sysconf = {"hostname": switches[switch],
               "timezone": base_config["timezone"],
               "radius_dynamic_authorization": {
                   "enable": True},
               "dhcpv4_snooping_general_configuration": {
                   "enable": True},
               }
    # We log in our device
    login = sesion.post(f"https://{switch}/rest/v10.12/login", data=credentials, verify=False)
    try:
        # Patching hostname, timezone and DHCP Snooping
        step_1 = sesion.patch(f"https://{switch}/rest/v10.12/system", json=sysconf, verify=False)
        print("step_1 " + str(step_1.status_code))
        # Patching DNS servers and domain_name
        step_2 = sesion.patch(f"https://{switch}/rest/v10.12/system/vrfs/default", json=dns_dhcp_auth_srv_config, verify=False)
        print("step_2 " + str(step_2.status_code))
        # Posting NTP Server
        step_3 = sesion.post(f"https://{switch}/rest/v10.12/system/vrfs/default/ntp_associations", json=ntp_server, verify=False)
        print("step_3 " + str(step_3.status_code))
        # Posting Syslog Server
        step_4 = sesion.post(f"https://{switch}/rest/v10.12/system/syslog_remotes", json=syslog_server, verify=False)
        print("step_4 " + str(step_4.status_code))
        # Posting Radius Dynamic Authentication Client
        step_5 = sesion.post(f"https://{switch}/rest/v10.12/system/vrfs/default/radius_dynamic_authorization_clients", json=radius_dyn_auth_client, verify=False)
        print("step_5 " + str(step_5.status_code))
        # Posting AAA Group
        step_6 = sesion.post(f"https://{switch}/rest/v10.12/system/aaa_server_groups", json=radius_server_groups, verify=False)
        print("step_6 " + str(step_6.status_code))
        # Posting Radius Servers
        step_7 = sesion.post(f"https://{switch}/rest/v10.12/system/vrfs/default/radius_servers", json=radius_server, verify=False)
        print("step_7 " + str(step_7.status_code))
        # Patching SSH and HTTPS login priorities
        # This enables admin login using radius
        step_8 = sesion.patch(f"https://{switch}/rest/v10.12/system/aaa_server_group_prios/ssh", json=aaa_prio_ssh_https, verify=False)
        print("step_8 " + str(step_8.status_code))
        step_9 = sesion.patch(f"https://{switch}/rest/v10.12/system/aaa_server_group_prios/https-server", json=aaa_prio_ssh_https, verify=False)
        print("step_9 " + str(step_9.status_code))
        # Creating VLANS
        for vlan in base_config["vlans"]:
            vlan_config["id"] = vlan
            vlan_config["name"] = base_config["vlans"][vlan]
            step_10 = sesion.post(f"https://{switch}/rest/v10.12/system/vlans", json=vlan_config, verify=False)
            print("step_10 " + str(step_10.status_code))
        # Assigning Voice VLAN
        step_11 = sesion.patch(f"https://{switch}/rest/v10.12/system/vlans/{base_config['voice_vlan']}", json=voice_vlan, verify=False)
        print("step_11 " + str(step_11.status_code))
        # Access port config
        for access_port in base_config["access_ports"]:
            step_12 = sesion.patch(f"https://{switch}/rest/v10.12/system/interfaces/{access_port.replace('/', '%2F')}", json=access_port_config, verify=False)
            print("step_12 " + str(step_12.status_code))
        # Server ports config
        for server_port in base_config["server_ports"]:
            step_13 = sesion.patch(f"https://{switch}/rest/v10.12/system/interfaces/{server_port.replace('/', '%2F')}", json=server_port_config, verify=False)
            print("step_13 " + str(step_13.status_code))
        # WAP ports config
        for wap_port in base_config["wap_ports"]:
            step_14 = sesion.patch(f"https://{switch}/rest/v10.12/system/interfaces/{wap_port.replace('/', '%2F')}", json=wap_port_config, verify=False)
            print("step_14 " + str(step_14.status_code))
        # Loging Out
        sesion.post(f"https://{switch}/rest/v10.12/logout")
    except Exception as e:
        print(e)



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this example, I iterate on the dictionary of switches and then apply the configuration iterating or not depending on what is being configured. I printed the state of each step just for basic troubleshooting and having some visibility of what's happening, but that's not necessary. You can instead use the response codes or text to create conditions and make your code more flexible and smarter (I'm not that creative when creating scripts). And see that depending on what I'm configuring, I use PATCH or POST, that information should come from the API documentation itself.&lt;/p&gt;

&lt;p&gt;That's it for this tutorial, I hope that this helps you to understand how to work with network devices' API interface and enables you to create amazing automation scripts!&lt;/p&gt;

&lt;p&gt;You can find the full script at my &lt;a href="https://github.com/julianalmanzar/network_automation_rest_api" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>networking</category>
      <category>automation</category>
    </item>
    <item>
      <title>Network Automation with Python and Paramiko</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sun, 03 Sep 2023 19:40:39 +0000</pubDate>
      <link>https://dev.to/julianalmanzar/network-automation-with-python-and-paramiko-4iof</link>
      <guid>https://dev.to/julianalmanzar/network-automation-with-python-and-paramiko-4iof</guid>
      <description>&lt;p&gt;In this tutorial I'll walk you through how you can automate tasks in your network devices through SSH using Python and the &lt;a href="https://docs.paramiko.org/en/latest/" rel="noopener noreferrer"&gt;Paramiko&lt;/a&gt; library. Paramiko is a Python implementation for SSH that allow us to connect to devices and execute commands on them, saving us time and reducing human errors when performing tasks.&lt;/p&gt;

&lt;p&gt;In this particular example, we'll configure DHCP snooping in every switch in a network.&lt;/p&gt;

&lt;h2&gt;
  
  
  DHCP Snooping
&lt;/h2&gt;

&lt;p&gt;DHCP snooping is a layer 2 security protocol that drops DHCP traffic on unauthorized interfaces, protecting the network from rogue DHCP servers. While protecting the network from rogue DHCP servers, you need to be able to accept the authorized DHCP servers too, so you need to specify the &lt;strong&gt;trusted interfaces&lt;/strong&gt; where the authorized traffic is coming from. The trusted interfaces usually are the Uplinks of every switch, but not every switch in the network has the interfaces correctly tagged or identified so, how do we know what port is the uplink in a switch? The answer resides in &lt;a href="https://www.fortinet.com/resources/cyberglossary/what-is-arp" rel="noopener noreferrer"&gt;ARP Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;Lets have a look at this diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq66yzt92t2i7qea1a7bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq66yzt92t2i7qea1a7bm.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In almost every network (at least on all of the networks I've worked with) the core of the network is the gateway of everything (servers, network devices, VoIP devices etc...) So it means that if I want to reach the internet or any other network, the traffic will flow through the network devices' &lt;strong&gt;uplink interfaces&lt;/strong&gt;. Knowing this, and knowing the IP of the default gateway, we can determine our uplink interfaces using the &lt;strong&gt;ARP Protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll use the ARP protocol to find the MAC Address of the default gateway and then, you'll find the port where that MAC address is learned from and that port is your Uplink. Some network devices provide the port information in the ARP table and you can save time on the lookup in the MAC table.&lt;/p&gt;

&lt;p&gt;Let's have a look at the diagram once again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlx5tnsyq9kovdix9mne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlx5tnsyq9kovdix9mne.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have identified our Uplink interfaces and also, we have a not authorized Wireless router with DHCP activated connected to our network (a very common case) that some user brought from home to use connect some wireless device to the corporate network.&lt;/p&gt;

&lt;p&gt;This device will inject DHCP in our network devices causing corporate devices to get IPs from the wireless router's network instead of the corporate one. To prevent this from happening, we just need to activate DHCP snooping and trust the uplink interfaces in every switch so the authorized DHCP server can still assign IPs to corporate network devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;From the previous section, we know what we need in order to configure DHCP snooping correctly in every network device:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's Uplink port: Using the ARP table and MAC table (or just the ARP table in some network devices)&lt;/li&gt;
&lt;li&gt;Activate DHCP Snooping: Using the devices CLI commands (consult your network device manual for that)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To get the Uplink port and configure DHCP snooping, I followed the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect to the device&lt;/li&gt;
&lt;li&gt;Get the MAC of the default gateway from the ARP table&lt;/li&gt;
&lt;li&gt;Get the port where the MAC of the default gateway is learned from using the MAC table&lt;/li&gt;
&lt;li&gt;Execute the command to activate DHCP Snooping and set the Uplink as a trusted interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To do that, I created the following functions to make the code easier to create:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import paramiko
import time
import re

'''
This function creates a SSH connection to a Device.
host is a dictionary that has the following structure:
host = {'hostname': 'IP or HOSTNAME', 'port': '22', 'username': 'username', 'password': 'passwd'}
'''


def create_connection(host):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(**host, look_for_keys=False, allow_agent=False)
    cli = ssh.invoke_shell()
    return ssh, cli


'''This function reads the output of the cli of a connection
I created this for the sole purpose of having a shorter way
to write the read command on the rest of the code.
'''


def read_output(cli):
    message = cli.recv(1000000000000000000000000).decode('utf-8')
    return message


'''
This function makes a lookup on the MAC Address Table
for the port where the provided mac address is learned from.

host is a dictionary that has the following structure:
host = {'hostname': 'IP or HOSTNAME', 'port': '22', 'username': 'username', 'password': 'passwd'}

You need to know how to find a mac addres in the target device.

The MAC address needs to be provided in the same format the switch supports.

The search command should be written as you would do on the console just without the MAC.

The port_regexp is a regluar expression that needs to match the names of all the possible interface
names on the switch.
'''


def get_port_mac(host, mac, search_command, port_regexp):
    try:
        ssh, cli = create_connection(host)
        cli.send(f'{search_command} {mac}\n')
        time.sleep(5)
        message = read_output(cli)
        port = re.findall(port_regexp, message, flags=re.IGNORECASE)
        ssh.close()
    except IndexError:
        print(host['hostname'] + " not available\n")
    return port[0]


'''
This function helps us to get the uplink of a network device using 
the gateway IP and ARP for this.

host is a dictionary that has the following structure:
host = {'hostname': 'IP or HOSTNAME', 'port': '22', 'username': 'username', 'password': 'passwd'}

The arp_search_command should be the search command you type o the device console when
searching for an ARP record, just without the IP.

The mac_search_command should be written as you would do on the console just without the MAC.

The port_regexp is a regluar expression that needs to match the names of all the possible interface
names on the switch.

What happens here is basically we add 1 command to the search and reuse the get_mac_port function
'''


def get_uplink(host, gateway, arp_search_command, mac_search_command, port_regexp):
    try:
        ssh, cli = create_connection(host)
        cli.send(f'{arp_search_command} {gateway}\n')
        time.sleep(5)
        arp = read_output(cli)
        mac = re.findall('(?:[0-9A-Fa-f]{2}[:-]){5}(?:[0-9A-Fa-f]{2})', arp)
        ssh.close()
        port = get_port_mac(host, mac[0], mac_search_command, port_regexp)
    except IndexError:
        print(host['hostname'] + " not available\n")
    return port


'''
This function executes a single command directly on the target device and returns
the console output.

host is a dictionary that has the following structure:
host = {'hostname': 'IP or HOSTNAME', 'port': '22', 'username': 'username', 'password': 'passwd'}

Command should be a string
'''


def execute_command(host, command):
    try:
        ssh, cli = create_connection(host)
        cli.send(command + "\n")
        time.sleep(5)
        message = read_output(cli)
        ssh.close()
    except Exception as e:
        print(f"Error at: {host} ------&amp;gt; {e}".format(hostname=host, error=e))
    return message


'''
This function executes a list of commands on the target device.

host is a dictionary that has the following structure:
host = {'hostname': 'IP or HOSTNAME', 'port': '22', 'username': 'username', 'password': 'passwd'}

commands should be a list of strings
'''


def execute_bulk_commands(host, commands):
    try:
        ssh, cli = create_connection(host)
        for command in commands:
            cli.send(command + "\n")
            time.sleep(2)
        ssh.close()
    except Exception as e:
        print(f"Error at: {host} ------&amp;gt; {e}".format(hostname=host, error=e))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note: you need to know very well your network device's CLI so you can elaborate the regexp patterns and the search commands.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With this functions, I created a script to configure the devices on my network. In my case, I have two different kind of switches in my network. I have some FortiSwitches and Cisco SF300 so my regexp patterns and show commands probably will be different to what you'll need.&lt;/p&gt;

&lt;p&gt;The code that resulted from my needs is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import threading
import net_tools

'''
Creating all of the parameter for each device brand
in my case I had these but they could be different in your case.
'''

cisco_switches = ["cisco_switch_01", "cisco_switch_02"]
cisco_switches_arp_search = "show arp ip-address "
cisco_switches_mac_search = "show mac address-table address "
cisco_switches_port_regexp = "gi[0123456789].{1,5}|fa[0123456789].{1,5}"

fortiswitches = ["fortiswitch_01", "fotiswitch_02"]
fortiswitches_arp_search = "diagnose ip arp list | grep "
fortiswitches_mac_search = "diagnose switch mac-address list | grep "
fortiswitches_port_regexp = "port[0-9]{1,2}"

# Creating the connection information. We´ll substitute the hostname later

connection_info = {'hostname': "",
                   'port': '22',
                   'username': 'username',
                   'password': 'password'}

gateway = "172.20.90.1"

cisco_switches_threads = list()
for hostname in cisco_switches:
    # We substitute the value of the hostname in the dictionary to match the target device
    # and we create a thread for each switch so the commands can be executed faster using multi-threading
    connection_info['hostname'] = hostname
    uplink_port = net_tools.get_uplink(connection_info, gateway, cisco_switches_arp_search,
                                       cisco_switches_mac_search, cisco_switches_port_regexp)
    cisco_switches_commands = ["enable",
                               "configure terminal",
                               "ip dhcp snooping",
                               "ip dhcp snooping database",
                               "ip dhcp snooping vlan 1",
                               f"interface {uplink_port}",
                               "ip dhcp snooping trust",
                               "end",
                               "wr",
                               "Y"]
    th = threading.Thread(target=net_tools.execute_bulk_commands, args=(connection_info, cisco_switches_commands))
    cisco_switches_threads.append(th)

fortiswitches_threads = list()
for hostname in fortiswitches:
    # We substitute the value of the hostname in the dictionary to match the target device
    # and we create a thread for each switch so the commands can be executed faster using multi-threading
    connection_info['hostname'] = hostname
    uplink_port = net_tools.get_uplink(connection_info, gateway, fortiswitches_arp_search,
                                       fortiswitches_mac_search, fortiswitches_port_regexp)
    fortiswitches_commands = ["config switch vlan",
                              "edit 1",
                              "set dhcp-snooping enable",
                              "end",
                              "config switch interface",
                              f"edit {uplink_port}",
                              "set dhcp-snooping trusted",
                              "end"]
    th = threading.Thread(target=net_tools.execute_bulk_commands, args=(connection_info, fortiswitches_commands))
    fortiswitches_threads.append(th)


# We start our threads
for thread in cisco_switches_threads:
    thread.start()

for thread in fortiswitches_threads:
    thread.start()

# We wait for all of them to finish
for thread in cisco_switches_threads:
    th.join()

for thread in fortiswitches_threads:
    th.join()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the files in my &lt;a href="https://github.com/julianalmanzar/network_automation" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository if you want to do something else on top of that, basically all you need to know is how to do it manually in the target devices and automate it!.&lt;/p&gt;

&lt;p&gt;If you have any doubts, contact me ans I'll answer you as soon as possible.&lt;/p&gt;

&lt;p&gt;Hope this post help some fellow network engineer with difficulties!&lt;/p&gt;

</description>
      <category>tutotial</category>
      <category>python</category>
      <category>networking</category>
      <category>automation</category>
    </item>
    <item>
      <title>Add Linux Server to Active Directory Domain with Ansible</title>
      <dc:creator>Julian</dc:creator>
      <pubDate>Sat, 26 Aug 2023 03:29:25 +0000</pubDate>
      <link>https://dev.to/julianalmanzar/add-linux-server-to-active-directory-domain-with-ansible-3oo3</link>
      <guid>https://dev.to/julianalmanzar/add-linux-server-to-active-directory-domain-with-ansible-3oo3</guid>
      <description>&lt;p&gt;If you're a new Systems Administrator that faces the challenge of creating local users for every Linux server manually on your infrastructure, this post is for you.&lt;/p&gt;

&lt;p&gt;Imagine you´re new in a place where you have to manage 30+ Linux servers and nobody else knows how to create users and give others access. Creating users in each and every single one of them is a headache right?&lt;/p&gt;

&lt;p&gt;Well, that led me to use a time-saving approach where creating users becomes a task of the actual responsible department not mine and, to make the hardening of your infrastructure more robust.&lt;/p&gt;

&lt;p&gt;Lacking of everything because of budget, I decided to manually add the servers to an Active Directory domain and restrict the access to an specific group of users. With that solution, security department only needs to add a user to that group and that's it.&lt;/p&gt;

&lt;p&gt;First, I researched on how to add Linux Servers to an Active Directory domain and I found &lt;a href="https://www.linuxtechi.com/integrate-rhel7-centos7-windows-active-directory/"&gt;this&lt;/a&gt; for CentOS and &lt;a href="https://codymoler.github.io/tutorials/ubuntu-active-directory/"&gt;this&lt;/a&gt; for Debian/Ubuntu distros.&lt;/p&gt;

&lt;p&gt;After doing a couple of them, I decided to create an Ansible playbook out of these guides because if I didn´t, I´d be still adding servers to the domain at the time of writing this.&lt;/p&gt;

&lt;p&gt;To do so, I basically created two playbooks in one, and they were executed depending on the distro of the current target server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing before the playbook
&lt;/h2&gt;

&lt;p&gt;Before anything, I created a vault where I stored these variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Linux OS credentials&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Windows Account with rights to add servers to the domain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The domain name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The domain controller´s name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The domain controllers´s IP&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Active Directory user group allowed to connect to the servers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created the vault using the command &lt;code&gt;ansible-vault create FILENAME --vault-password-file PASSWORDFILE&lt;/code&gt; where &lt;code&gt;FILENAME&lt;/code&gt; is the actual vault that contains the Ansible variables and &lt;code&gt;PASSWORDFILE&lt;/code&gt; is a text file that only contains the password for unlocking the vault.&lt;/p&gt;

&lt;p&gt;After that, you can edit the vault using &lt;code&gt;ansible-vault edit FILENAME --vault-password-file PASSWORDFILE&lt;/code&gt; and add the following lines to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server_username: linuxuser
server_password: linuxpassword
domain_name: example.do
domain_controller_ip: X.X.X.X
domain_controller_name: controller
domain_admin_user: adminuser
domain_admin_password: adminpassword
domain_group_allowed: allowedgroup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note: The Linux user needs root permissions and the domain name should be all caps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After having your vault correctly created, you have to add the target servers in your hosts file that resides by default in &lt;code&gt;/etc/ansible/hosts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the hosts file for this, I used the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Group_name]
SERVER1_HOSTNAME ansible_host=1.1.1.1
SERVER2_HOSTNAME ansible_host=2.2.2.2
.....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can group the servers as you want, but because of how the playbook is made, you won't need more than one group for this to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Playbook
&lt;/h2&gt;

&lt;p&gt;After creating our vault with our variables, and having all of our target servers in the hosts file, we can start creating our playbook. First, we name our playbook and configure the parameters as shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
- name: Add Linux Server to Domain
  gather_facts: true
  hosts: Group_name
  vars:
    ansible_become: yes
    ansible_become_method: sudo
    ansible_user: "{{ server_username  }}"
    ansible_ssh_pass: "{{ server_password  }}"
    ansible_become_pass: "{{ server_password  }}"

    credentials: {
               'username': "{{ domain_admin_user }}",
               'password': "{{ domain_admin_password }}"
              }
    domain_name: "{{ domain_name }}"
    domain_controller_ip: "{{ domain_controller_ip }}"
    domain_controller_name: "{{ domain_controller_name }}"
    domain_group_allowed: "{{ domain_group_allowed }}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this part, we name our playbook and set the gather facts to true so we can use them later to take decisions. We also define the target hosts group that this playbook will be executed on and then, we reference our variables. The variables that start with &lt;code&gt;ansible_&lt;/code&gt; are default variables, that don't need to be referenced later because they define values used by Ansible functions like elevating privileges (sudo). The following variables have the values that we specified on our vault file, in this example I used the same names in the vault and in the Ansible playbook but this is not necessary, but yo have to be carefull so you don't misplace any value.&lt;/p&gt;

&lt;p&gt;Then we create our tasks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  tasks:

    - name: Install CentOS Packages
      yum:
        name:
          - sssd
          - realmd
          - oddjob
          - oddjob-mkhomedir
          - adcli
          - samba-common
          - samba-common-tools
          - krb5-workstation
          - openldap-clients
          - policycoreutils-python
        state: present
      when: ansible_distribution == "CentOS"

    - name: Update Hosts File
      lineinfile:
        dest: "/etc/hosts"
        line: "{{ domain_controller_ip }}   {{ domain_controller_name }}.{{ domain_name }}   {{ domain_controller_name }}"
        state: present
        backup: yes
      when: ansible_distribution == "CentOS"

    - name: Join Realm
      shell:
        "echo '{{ credentials['password'] }}' | sudo realm join --user={{ credentials['username'] }} {{ domain_name }}"
      register: result
      when: ansible_distribution == "CentOS"
    - name: Print Realm Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "CentOS"

    - name: Modify SSDCONF Line 1
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'use_fully_qualified_names = True'
        replace: 'use_fully_qualified_names = False'
      when: ansible_distribution == "CentOS"
    - name: Modify SSDCONF Line 2
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'fallback_homedir = /home/%u@%d'
        replace: 'fallback_homedir = /home/%u'
      when: ansible_distribution == "CentOS"

    - name: Restart sssd
      shell:
        "sudo systemctl restart sssd"
      when: ansible_distribution == "CentOS"
    - name: Daemon Reload
      shell:
        "sudo systemctl daemon-reload"
      when: ansible_distribution == "CentOS"

    - name: Add {{ domain_group_allowed }} to Sudoers
      lineinfile:
        dest: "/etc/sudoers"
        line: "%{{ domain_group_allowed }}   ALL=(ALL)       NOPASSWD: ALL"
        state: present
        backup: yes
      when: ansible_distribution == "CentOS"

    - name: Allow Only {{ domain_group_allowed }} CentOs Login
      shell:
        "realm permit -g {{ domain_group_allowed }}@{{ domain_name }}"
      when: ansible_distribution == "CentOS"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first group of tasks correspond to the ones that will run on CentOS distros. In this case, we basically follow step by step the &lt;a href="https://www.linuxtechi.com/integrate-rhel7-centos7-windows-active-directory/"&gt;guide&lt;/a&gt; for adding a CentOS server to active directory using whatever Ansible has to solve this. At the end of each step, there's a condition that specifies that each task will run in the current target server only if it's OS distribution is equal to the condition, being CentOS in this case.&lt;/p&gt;

&lt;p&gt;The same way, the Ubuntu tasks were made following the previous &lt;a href="https://codymoler.github.io/tutorials/ubuntu-active-directory/"&gt;guide&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    - name: Apt Update
      shell:
        "sudo apt-get update -y"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Install Debian/Ubuntu Packages
      apt:
        name:
          - sssd-ad
          - sssd-tools
          - realmd
          - adcli
          - packagekit
        state: present
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Create KRB5 File
      copy:
        dest: "/etc/krb5.conf"
        content: |
          [libdefaults]
          default_realm = {{ domain_name }}
          ticket_lifetime = 24h
          renew_lifetime = 7d
          dns_lookup_realm = false
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Install Debian/Ubuntu Remaining Packages
      apt:
        name:
          - krb5-user
          - sssd-krb5
        state: present
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Modify Hosts File
      lineinfile:
        dest: "/etc/hosts"
        line: "127.0.1.1       {{ ansible_hostname }}.{{ domain_name }}"
        state: present
        backup: yes
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Modify Hostname
      shell:
        "sudo hostnamectl set-hostname {{ ansible_hostname }}.{{ domain_name }}"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Kinit
      shell:
        "echo {{ credentials['password'] }} | sudo kinit {{ credentials['username'] }}"
      register: result
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Print Kinit Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Join Realm Ubuntu
      shell:
        "echo {{ credentials['password'] }} | sudo realm join -v -U {{ credentials['username'] }} {{ domain_name }}"
      register: result
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Print Realm Result
      debug: var=result.stdout_lines
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Activate Homedir Creation
      shell:
        "sudo pam-auth-update --enable mkhomedir"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Modify SSDCONF Line 1 Ubuntu
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'use_fully_qualified_names = True'
        replace: 'use_fully_qualified_names = False'
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
    - name: Modify SSDCONF Line 2 Ubuntu
      replace:
        path: /etc/sssd/sssd.conf
        regexp: 'fallback_homedir = /home/%u@%d'
        replace: 'fallback_homedir = /home/%u'
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Add {{ domain_group_allowed }} to Sudoers Ubuntu
      lineinfile:
        dest: "/etc/sudoers"
        line: "%{{ domain_group_allowed }}   ALL=(ALL) NOPASSWD:ALL"
        state: present
        backup: yes
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

    - name: Allow Only {{ domain_group_allowed }} Login Ubuntu
      shell:
        "realm permit -g {{ domain_group_allowed }}@{{ domain_name }}"
      when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can execute the playbook using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ansible-playbook -i [path/to/hostsfile] [path/to/playbookfile] -e@./[path/to/vaultfile]  --vault-password-file ~/[path/to/passwordfile]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing this, your server will be added to the Active Directory Domain and you'll be ready to handle users in a more secure and efficient way.&lt;/p&gt;

&lt;p&gt;I hope this helps someone out there, for me this was something that made my days easier to handle.&lt;/p&gt;

&lt;p&gt;The code is available at my &lt;a href="https://github.com/julianalmanzar/linuxserver_to_domain"&gt;GitHub&lt;/a&gt; profile.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>linux</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
