Whenever I build out a new instance of Nautobot, I struggle with where to start. I try to add a Device, only to find that I need to create a Device Type, Site, and Device Role. I go to add a Device Type and find I need a Manufacturer (which is not directly required for a Device). It would be great if Nautobot would alert me when I am missing required objects.
Speaking of Device Types, I find that I am adding numerous Device Types to Nautobot. There is a nice community resource DeviceType-library to add Device Types into Nautobot, but tracking down the right YAML file to then import into Nautobot takes time.
The Welcome Wizard is an open-source Nautobot plugin with the goal to assist users with the necessary initial steps in populating data within Nautobot.
The Welcome Wizard adds four (4) key features:
Import Wizard
Welcome Wizard uses the Import Wizard to allow ease of adding community-defined Device Types and Manufacturers into Nautobot. This is built upon the Git datasources feature of Nautobot.
Quick-Start Settings
Welcome Wizard includes by default the DeviceType-library, but this can be disabled and a custom library can be used instead.
Helpful Middleware
Welcome Wizard includes banners in forms to alert the user when required form fields have no associated resources in Nautobot.
Welcome Wizard Dashboard
The Welcome Wizard Dashboard contains a list of common Nautobot Data Models that many other Nautobot models require. This page allows ease of adding items to Nautobot or, if supported, importing them. This ties all of the features together.
Installing the Plugin
The plugin is available as a Python package in PyPI and can be installed atop an existing Nautobot installation using pip:
pip install nautobot-welcome-wizard
This plugin is compatible with Nautobot 1.0.0b4 and higher. Once installed, the plugin needs to be enabled in your nautobot_config.py:
You can begin by selecting Plugins -> Nautobot Welcome Wizard -> Welcome Wizard from the navigation bar (or navigating to /plugins/welcome_wizard/) on your Nautobot instance to view the Welcome Wizard dashboard:
Welcome Wizard shows a set of Nautobot objects that lay the groundwork for other Nautobot objects. The goal of the dashboard is to help introduce features of Nautobot and keep track of their use. For instance, Sites are used in a number of objects inside of Nautobot. A Site is used when creating Devices, Racks, and Rack Groups. In addition, they are optional in many other objects.
From the dashboard, you can click on the green Add button in the Sites role to take you directly to the form for adding a Site. For Manufacturers and Device Types, you can click on the blue wizard hat to take you to the Welcome Wizard Import page.
Welcome Wizard Import
Welcome Wizard allows easy import from a Device Type library for Manufacturers and Device Types. Let’s take a look at the Device Type Import. From the dashboard, click on the blue wizard hat in the Device Types row.
Note if this is the first time loading this page, it will take a minute to synchronize the data with the Git Datasource.
On this page you can search for a Device Model or filter by Manufacturer. To import a Device Type into Nautobot, click the blue import button next to the Device Model you are trying to import. Once you confirm the import, the Device Type will now be available for use in Nautobot.
Conclusion
More information on this plugin can be found at Nautobot Welcome Wizard Docs. We hope this plugin will allow you to quickly and easily get started in Nautobot.
Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.
This blog post aims to assist Ansible users in providing documentation to security teams when using privileged escalation (become) in Ansible playbooks.
The Problem Statement
Ansible does not use a specific command when using privileged escalation on remote machines see here for more details. As an example, if you wanted to restart the httpd service, the command you run on the system would be sudo systemctl restart httpd but when Ansible executes the task, you would see something like /bin/sh BECOME-SUCCESS ; /usr/bin/python /home/myuser/.ansible/tmp/ansible-tmp-abcdef/AnsiballZ_12345.py. Here is a link explaining the Ansiballz framework. This presents two concerns to most security teams. First, they cannot limit the commands used by Ansible. Second, they cannot see which commands Ansible executes. We will be addressing the second concern.
The Solution
Ansible provides Callback Plugins that can be used when Ansible responds to various events inside a playbook. Ansible provides a small set of default callback plugins out of the box, but none that address our problem.
Custom Callback Plugin
We will be creating a custom callback plugin that hooks into the use of become and logs to stdout. This will not log the actual command used by Ansible but the module and arguments passed to the module. As an example, instead of systemctl restart httpd the log would show the ansible.builtin.service module with the name set to httpd and the state set to restarted.
First we need to build out our directory structure. We are choosing the name “become” for our plugin, but you could choose your own name. By convention, the filename and plugin name should match, but they do not have to. This assumes the callback plugin will be added to an existing Ansible project:
Then add the callback plugin to your ansible.cfg file.
# ansible.cfg[defaults]callback_whitelist = become
From there we will create a sample playbook that restarts httpd:
# example_pb.yaml----hosts: alltasks:-name:"ESCALATED TASK: RESTART HTTPD" ansible.builtin.service:name: httpdstate: restartedbecome:true-name:"NON-ESCALATED TASK: DEBUG" ansible.builtin.debug:msg:"This task does not require escalation"
Next we will add the basic structure to our callback plugin. Ansible provides an example plugin that we will modify.
# become.py# Make coding more python3-ish,this is required for contributions to Ansiblefrom __future__ import (absolute_import, division, print_function)__metaclass__ = type# not only visible to ansible-doc, it also 'declares' the options the plugin requires and how to configure them.DOCUMENTATION =''' callback: becomerequirements:- whitelist in configurationshort_description: When become is used, log to stdout.description:- This callback will log the become, become method, task name, action and arguments to stdout.'''from ansible.plugins.callback import CallbackBaseclassCallbackModule(CallbackBase): """Thiscallbackmoduletellsyouwhenbecomeisused. """CALLBACK_VERSION = 2.0CALLBACK_NAME = 'become' # onlyneededifyoushipitanddon'twanttoenablebydefaultCALLBACK_NEEDS_WHITELIST = Truedef__init__(self): # makesuretheexpectedobjectsarepresent, callingthebase's__init__super(CallbackModule, self).__init__()
We have now added the skeleton that Ansible requires, but we have not added any new functionality to our plugin. Callback plugins listen for events that Ansible emits. Check out the callback init file in the Ansible repository for a list of all the events supported. The event that we will be looking at hooking into is the v2_runner_on_start, since it passes not only the task but also the host, and it executes at the start of a task. Let’s add our first callback now.
We are accessing attributes of task and host here. In this example we are showing just a few, but you can use dir(task) or dir(host) to find many others. When we execute the playbook, we should see the following:
At this point, We have a callback plugin that will log to stdout the use of become in our playbooks. But if our playbook uses variables, we will not see those variables. Let’s modify our example to show:
# example_pb.yaml----hosts: alltasks:-name:"ESCALATED TASK: RESTART HTTPD" ansible.builtin.service:name:"{{ service }}"state: restartedbecome:truevars:service: httpd-name:"NON-ESCALATED TASK: DEBUG" ansible.builtin.debug:msg:"This task does not require escalation"
Now let’s run the playbook:
TASK [Gathering Facts]****************************ok: [my_hostname]TASK [ESCALATED TASK: RESTART HTTPD]**************Hostname: my_hostnameTask: ESCALATED TASK: RESTART HTTPDAction: ansible.builtin.serviceArguments:{'name': '{{ service }}','state': 'restarted'}changed: [my_hostname]TASK [NON-ESCALATED TASK: DEBUG]ok: [my_hostname] =>{"msg": "This task does not require escalation"}
In order to access variables passed into a task (or from group vars), we need to add the Templar class to our callback plugin. We will need to add a few more hooks to get all of the play data and the hostvars.
# become.pyfrom ansible.plugins.callback import CallbackBasefrom ansible.template import Templarclass CallbackModule(CallbackBase):... def v2_playbook_on_start(self, playbook):""" Initialize self.playbook.""" self.playbook = playbook def v2_playbook_on_play_start(self, play):""" Get the variable manager of the current play from the playbook.""" self.play = play self.vm = play.get_variable_manager() def _all_vars(self, host=None, task=None):""" Load all variables for the given inputs.""" return self.vm.get_vars( play = self.play, host = host, task = task ) def v2_runner_on_start(self, host, task):""" Process the runner on start event.""" templar = Templar(loader=self.playbook.get_loader(), variables=self._vars(host=host, task=task)) if task.become: print(f"Hostname: {host.name}") print(f"Task: {task.name}") print(f"Action: {task.action}") print(f"Arguments: {templar.template(task.args, fail_on_undefined=False)}")
With the above changes, running the playbook will now fill in the variables:
Now that we know how to access the task attributes and variables, we could extend any of the other popular callback plugins, such as syslog_json, Logstash, or Splunk, to send the data directly to the security team.
There are a few Ansible playbook options that are not covered here, such as loop or with_items, become_method, or become_user, but this should be enough to get you started. Another suggested change would be to set CALLBACK_NEEDS_WHITELIST = False. With this change, we would no longer need to whitelist become in the ansible.cfg file. This, paired with Ansible Tower or AWX, would allow the security team to ensure conformance to standards. For this to work, the plugin would need to be moved to the environment created by Ansible Tower or AWX. See Adding a plugin locally for more details.
Conclusion
Ansible callback plugins give us the ability to provide detailed evidence of our privileged escalation to answer some of the security team’s concerns.
Thanks for taking the time to read!
-Stephen
Here is the full code from become.py:
# become.py# Make coding more python3-ish,this is required for contributions to Ansiblefrom __future__ import (absolute_import, division, print_function)__metaclass__ = type# not only visible to ansible-doc, it also 'declares' the options the plugin requires and how to configure them.DOCUMENTATION =''' callback: becomerequirements:- whitelist in configurationshort_description: Logs become use to stdoutdescription:- This callback will log the become, become method, task name, action and arguments to stdout.'''from ansible.plugins.callback import CallbackBasefrom ansible.template import Templarclass CallbackModule(CallbackBase):""" This callback module tells you when become is used.""" CALLBACK_VERSION = 2.0 CALLBACK_NAME = 'become' # only needed if you ship it and don't want to enable by default CALLBACK_NEEDS_WHITELIST = True def __init__(self): # make sure the expected objects are present, calling the base's __init__ super(CallbackModule, self).__init__() def v2_playbook_on_start(self, playbook):""" Initialize self.playbook.""" self.playbook = playbook def v2_playbook_on_play_start(self, play):""" Get the variable manager of the current play from the playbook.""" self.play = play self.vm = play.get_variable_manager() def _all_vars(self, host=None, task=None):""" Load all variables for the given inputs.""" return self.vm.get_vars( play = self.play, host = host, task = task ) def v2_runner_on_start(self, host, task):""" Process the runner on start event.""" templar = Templar(loader=self.playbook.get_loader(), variables=self._vars(host=host, task=task)) if task.become: print(f"Hostname: {host.name}") print(f"Task: {task.name}") print(f"Action: {task.action}") print(f"Arguments: {templar.template(task.args, fail_on_undefined=False)}")
Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies. In case of sale of your personal information, you may opt out by using the link Do not sell my personal information. Privacy | Cookies
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
__hssc
30 minutes
HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
__hssrc
session
This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category.
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent
1 year
CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Cookie
Duration
Description
__cf_bm
30 minutes
Cloudflare set the cookie to support Cloudflare Bot Management.
li_gc
5 months 27 days
Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.
lidc
1 day
LinkedIn sets the lidc cookie to facilitate data center selection.
UserMatchHistory
1 month
LinkedIn sets this cookie for LinkedIn Ads ID syncing.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Cookie
Duration
Description
__hstc
5 months 27 days
Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).
_ga
1 year 1 month 4 days
Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.
_gat_gtag_UA_*
1 minute
Google Analytics sets this cookie to store a unique user ID.
_gid
1 day
Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously.
AnalyticsSyncHistory
1 month
Linkedin set this cookie to store information about the time a sync took place with the lms_analytics cookie.
CONSENT
2 years
YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data.
hubspotutk
5 months 27 days
HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.
ln_or
1 day
Linkedin sets this cookie to registers statistical data on users' behaviour on the website for internal analytics.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Cookie
Duration
Description
bcookie
1 year
LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.
bscookie
1 year
LinkedIn sets this cookie to store performed actions on the website.
li_sugr
3 months
LinkedIn sets this cookie to collect user behaviour data to optimise the website and make advertisements on the website more relevant.
VISITOR_INFO1_LIVE
5 months 27 days
YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface.
YSC
session
Youtube sets this cookie to track the views of embedded videos on Youtube pages.
yt-remote-connected-devices
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt-remote-device-id
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt.innertube::nextId
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
yt.innertube::requests
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.