Project 1 – Ansible – Deploy New Network & Network Security – Stage 4 – Deploy ACL’s
Welcome back, there has been a brief delay in the regularity of this post due to simply not knowing a few things and having to learn them. This is precisely the point of this project after all.
Now at the start of this project I did state there would be a situation where I would need to change my steps for reasons at the time I did not consider. I have decided that in order to keep things simple and moving forward I will be focussing on just deploying ACL’s to the VLAN interface on the core switch. This next stage is sufficient to demonstrate deploying a level of network security to network devices. It actually will demonstrate the simple but actually very helpful object-groups and their usage within an ACL. In the future project, I will be adding in the Palo deployment stages. The Palo Ansible automation, actually is quite a project on it’s own right or at least quite a large post which I didn’t think would be reasonable to do in this project and would bog me down somewhat. I am following the KISS principle for this project.
So onto the playbook at hand, I will explain some of the design choices and what I was trying to achieve with this playbook in what I hope is a much better way than previous posts.
There isn’t a full posting of the files, but if you want to see them all, please feel free to get them from my GitHub repo.
https://github.com/danielbostock/Project1-Ansible-Deploy_NewNetwork-SecPol
Pre_tasks Summary
So some of you who have been following along will recognise my usage of a pre_tasks portion and why I am doing this. For those who do not know, I have chosen to use pre_tasks (and post_tasks) as a way of organising the playbook mostly, whilst there are direct technical benefits it is mostly a playbook organisation benefit to me.
Comparing startup and running config
# --- Begin the pre-flight checks - backup, obtain current interface configuration, current running configurations --- # IF this is a new network device deployment these pre_tasks checks will often be unecessary and I recommend commenting or removing them. pre_tasks: # Check if there are no current outstanding and not committed configuration changes - pause: seconds: 2 prompt: "Pre-flight checklists for deployment are beginning. This first step is checking if there are outstanding changes on the target device." - name: Compare the current Startup vs Running configuration to confirm no outstanding changes nxos_config: diff_against: intended intended_config: "{{ lookup('file', '~/Documents/Training/Network_Automation/Ansible/Lab03-Network/pre_dep_backups/pre_dep_startup/{{ inventory_hostname }}_pre_dep_startup.cfg') }}" - pause: prompt: "Press enter to continue if there are no outstanding changes. If there are; proceed with caution and awareness, as this playbook will deploy these changes as well."
Why do this? Well for me as a network engineer with only around 2 years of experience (so some of you who have many many more years will have some fun stories here I am sure!), I have had the lovely joy of seeing many a device where the running config was never actually saved and because network devices don’t often get restarted no one ever noticed this. Therefore this check is to ensure there are no outstanding config there that shouldn’t be because at the end of this playbook the configuration will be written and any changes that are not written should be known about, confirmed and audited in my opinion. Now there are caveats with this, you need to ensure that file you reference to diff against is a copy of the startup config otherwise it isn’t going to show a diff.
I have actually thought about building this as a regular Ansible Playbook script to run over my network environment to ensure that configurations that were made were written or if they weren’t meant to be there then we can audit/investigate this. Anyway pressing on…
Backup Config
# Begin backup, modify backup directory as required - name: Backup of the current running config before deploying changes to the device nxos_config: backup: yes backup_options: dir_path: ~/Documents/Training/Network_Automation/Ansible/Lab03-Network/pre_dep_backups/pre_dep_startup/ host: "{{ inventory_hostname }}"
I can’t stress enough how important it is before deployment to backup your config. I build this into every playbook I do, even if I have an awesome backup system, I still backup to my device locally before each deployment. Now one thing I have already noticed with doing all these playbooks is that I have to copy and paste this each time into my playbook, but surely Ansible have a way of not needing this? Yep they do, they have several actually and indeed the recommended approach would be to make this a role. Which I believe I will do soon, the next easy way to do it would be to import_task and grab this task in a playbook which I choose to put a lot of these basic plays into. These are probably one of the two refinement approaches I am considering as part of the next project, for now keeping with KISS!!!
Display the new configuration to be deployed
# Obtain and Display new configuration to the deployer to show the configuration that they will be deploying - name: Address & Port Object-Groups for ACL's that will be created debug: msg: "{{ lookup('template', './project1_stage4_addrobjects_template.j2') }}" - pause: prompt: "Confirm that all ACL Address Objects are correct... Press CTRL+C to begin abort sequence." - name: Access Control Lists that will be created debug: msg: "{{ lookup('template', './templates/project1_stage4_acl_template.j2') }}" - pause: prompt: "Confirm that all Access Control Lists and their respective entries are correct... Press CTRL+C to begin abort sequence."
Just like with backing up, I can’t stress how important this is as well. This forces me to look again at the changes I want to deploy. This makes me press enter to confirm that yep all the changes look good. The changes being made here, if in a production environment could cause outages if not applied correctly, so again it is pertinent to double or triple check even.
Tasks Summary
So this portion is actually quite small in the playbook deployment part, however all the work is being done in the Jinja2 templates, I chose to do it this way because this project was focussed on deploying through Jinja2 templates. I could have deployed chances directly with the module for NXOS ACL’s, however due to the amount of ACL’s it is more efficient to use a template. If I was deploying one or two ACL’s, most definitely I would use the module.
Task: Create Object Groups
Playbook
- name: Display Task - Creat Object-groups - Address & Port object-groups debug: msg: "{{ lookup('template', './project1_stage4_addrobjects_template.j2') }}" - pause: prompt: "Confirm that all ACL Address Objects are correct... Press CTRL+C to begin abort sequence."
Address Objects Jinja2 Template
{% for object in lab1_cs1_objects_addr %} object-group ip address {{object.name}} {% for network in object.networks %} {{network.address}} {% endfor %} {% endfor %} {% for object in lab1_cs1_objects_port %} object-group ip port {{object.name}} {% for protocol in object.protocols %} eq {{protocol.port}} {% endfor %} {% endfor %}
So believe it or not y’all, this took me a good part of a week to figure out how I was going to create all these objects. There were several ways to do it and the tricky part was the nesting of iterated items to create. So yeah I needed to learn how to make nested loops with Ansible leveraging Jinja2 templating, whilst I also learning about how referencing of vars files also worked more completely as I weighed up ways I go deploy this series of changes.
I ended up on a template like this which helps to be easily read later on and also quite easy to be leveraged any time in the future or say when I decide to final make this a role. As a little bit of a gotcha, as this was something that was not shown to me in my initial jinja2 learning labs because they used group_vars or host_vars which Ansible automatically knows the location of the yaml file to iterate through. I got a little stumped as to how to reference the yml file (ie: lab1_cs1_objects_addr listed in the first loop). Well I found the best way to do this was to do it at the start of the playbook with the following:
vars_files: - ./vars/lab1_cs1_objects_addr.yml - ./vars/lab1_cs1_objects_port.yml - ./vars/lab1_cs1_acl.yml
Simple little gotcha, but was well worth learning. I now know also there are many ways to pull these vars file or others through the Jinja2 templating, but this is what I got working and am sticking with for this stage in the project.
So in order to make sense of this Jinja2 template, it is helpful to know what it is looping through. Lets start with the first loop.
Address Objects Loop Items
lab1_cs1_objects_addr: - addr_obj_grp1: name: NV-DMZ_ADDR networks: - address: 10.10.11.0/24 - address: 10.1.11.0/24 - addr_obj_grp2: name: LAB1-NTP_ADDR networks: - address: 10.10.11.0/24 - address: 10.10.12.0/24 - address: 192.168.255.22/32 - addr_obj_grp3: name: NV-MGMT_ADDR networks: - address: 10.0.99.0/24 - address: 10.1.99.0/24 - address: 10.0.0.30/32 - addr_obj_grp4: name: NV-NTP_ADDR networks: - address: 10.0.8.2/32 - address: 10.0.8.5/32 - address: 10.10.10.4/32 - addr_obj_grp5: name: NV-WAN_ADDR networks: - address: 10.0.0.0/8 - address: 10.0.0.30/32 - address: 192.168.0.0/16 - address: 172.16.0.0/12 - addr_obj_grp6: name: NV-DKR-HOSTS_ADDR networks: - address: 10.10.12.10/32 - addr_obj_grp7: name: NV-WEB-HOSTS_ADDR networks: - address: 10.10.11.10/32
Port Objects Loop Items
lab1_cs1_objects_port: - port_obj_grp1: name: LAB1-SRV-MGMT_PORTS protocols: - port: 22 - port: 443 - port: 123 - port: 53 - port_obj_grp2: name: LAB1-SECURE-WEB_PORTS protocols: - port: 443 - port: 53 - port: 20 - port_obj_grp3: name: LAB1-DOCKER-MC_PORTS protocols: - port: 25565 - port: 25566 - port: 25567 - port: 25568
So the structure of these vars files was the critical component that I was missing. The part where I was getting it wrong was creating the list with the “-“ on the address and port items. After understanding the purpose of this I was able to move on. But essentially the purpose is to create sub items that can be iterated over in the Jinja2 template.
Task: Create Access Control Lists
- name: Create Access Control Lists debug: msg: "{{ lookup('template', './templates/project1_stage4_acl_template.j2') }}" - pause: prompt: "Confirm that all Access Control Lists and their respective entries are correct... Press CTRL+C to begin abort sequence."
ACL Jinja2 Template
{% for acls in lab1_cs1_acl %} ip access-list {{acls.name}} {% for acl_ace in acls.acl_aces %} {{acl_ace.ACE}} {% endfor %} {% endfor %}
ACL Loop Items
lab1_cs1_acl: - access_control_list1: name: NV-SRV-VLAN_ACL acl_aces: - ACE: 10 permit udp addrgroup LAB1-NTP_ADDR eq ntp 10.10.10.0/24 eq ntp - ACE: 11 permit tcp addrgroup NV-WAN_ADDR 10.10.10.0/24 portgroup LAB1-SRV-MGMT_PORTS - ACE: 12 permit udp addrgroup LAB1-NTP-ALLOW eq ntp 10.10.10.4/32 eq ntp - ACE: 20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.10.0/24 - ACE: 21 permit udp any eq domain 10.10.10.0/24 - ACE: 23 permit tcp any 10.10.10.0/24 established - ACE: 200 deny ip addrgroup NV-DMZ_ADDR 10.10.10.0/24 - ACE: 300 permit icmp addrgroup NV-WAN_ADDR 10.10.10.0/24 - access_control_list2: name: NV-DMZ-VLAN_ACL acl_aces: - ACE: 10 permit udp 10.10.10.4/32 10.10.11.0/24 eq ntp - ACE: 11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS - ACE: 20 permit tcp any portgroup NV-SECURE-WEB_PORTS 10.10.11.10/32 - ACE: 21 permit udp any eq domain 10.10.11.0/24 - ACE: 22 permit tcp any 10.10.11.0/24 established - ACE: 50 permit udp addrgroup DKR-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup NV-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS - ACE: 51 permit tcp addrgroup DKR-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup NV-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS - ACE: 300 permit icmp addrgroup NV-MGMT_ADDR 10.10.11.0/24 - access_control_list3: name: LAB1-DOCKER-MC_PORTS acl_aces: - ACE: 10 permit udp 10.10.10.4/32 10.10.12.0/24 eq ntp - ACE: 11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS - ACE: 20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.12.10/32 - ACE: 21 permit udp any eq domain 10.10.12.0/24 - ACE: 22 permit tcp any 10.10.12.0/24 established - ACE: 50 permit udp addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS - ACE: 51 permit tcp addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS
Now this is where you get to see everything that was created in the object-groups come together. I could have drawn up a nice Visio design here on how these access lists affect the hosts and networks, but really you guys don’t need to waste your time looking at it. We are here to see playbooks getting made and run not a lesson ACL’s or network security, plus there are much better writers out there in that space. SO again, a similar structure to my item lists was employed here. I am trying to be consistent with my structure so it is easy to adjust or re-use.
ACL’s again are not a good security feature, so don’t think my praise of object groups means they are good, this is still a very low level security feature and can’t do that much because they are investigating the packets in a stateful manner. However object-groups make it really easy to automate and manage this deployment and future deployments because I use object groups and entries to object groups rather than new ACL’s where possible.
Object groups are also not limited to just ACL’s, they can also be used in other places such as route maps for example. A very handy feature and yet very simple to use and manage.
(venv) Daniels-MacBook-Pro:Lab03-Network daniel$ ansible-playbook project1_stage4_deploy_netsecurity.yml [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details PLAY [Project 1 - Stage 4 - Deploy Network Security to Core Switches] ************************************************************************************************************************************************************************************************************************************************* TASK [pause] ********************************************************************************************************************************************************************************************************************************************************************************************************** Pausing for 2 seconds (ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort) [pause] Pre-flight checklists for deployment are beginning. This first step is checking if there are outstanding changes on the target device.: ok: [NV-NET-LAB1-CS1] TASK [Compare the current Startup vs Running configuration to confirm no outstanding changes] ************************************************************************************************************************************************************************************************************************* [WARNING]: Platform darwin on host NV-NET-LAB1-CS1 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information. ok: [NV-NET-LAB1-CS1] TASK [pause] ********************************************************************************************************************************************************************************************************************************************************************************************************** [pause] Press enter to continue if there are no outstanding changes. If there are; proceed with caution and awareness, as this playbook will deploy these changes as well.: [[ok: [NV-NET-LAB1-CS1] TASK [Backup of the current running config before deploying changes to the device] ************************************************************************************************************************************************************************************************************************************ changed: [NV-NET-LAB1-CS1] TASK [Display Task - Creat Object-groups - Address & Port object-groups] ********************************************************************************************************************************************************************************************************************************************** ok: [NV-NET-LAB1-CS1] => msg: |- object-group ip address NV-DMZ_ADDR 10.10.11.0/24 10.1.11.0/24 object-group ip address LAB1-NTP_ADDR 10.10.11.0/24 10.10.12.0/24 192.168.255.22/32 object-group ip address NV-MGMT_ADDR 10.0.99.0/24 10.1.99.0/24 10.0.0.30/32 object-group ip address NV-NTP_ADDR 10.0.8.2/32 10.0.8.5/32 10.10.10.4/32 object-group ip address NV-WAN_ADDR 10.0.0.0/8 10.0.0.30/32 192.168.0.0/16 172.16.0.0/12 object-group ip address NV-DKR-HOSTS_ADDR 10.10.12.10/32 object-group ip address NV-WEB-HOSTS_ADDR 10.10.11.10/32 object-group ip port LAB1-SRV-MGMT_PORTS eq 22 eq 443 eq 123 eq 53 object-group ip port LAB1-SECURE-WEB_PORTS eq 443 eq 53 eq 20 object-group ip port LAB1-DOCKER-MC_PORTS eq 25565 eq 25566 eq 25567 eq 25568 TASK [pause] ********************************************************************************************************************************************************************************************************************************************************************************************************** [pause] Confirm that all ACL Address Objects are correct... Press CTRL+C to begin abort sequence.: [[ok: [NV-NET-LAB1-CS1] TASK [Display Task - Create Access Control Lists] ********************************************************************************************************************************************************************************************************************************************************************* ok: [NV-NET-LAB1-CS1] => msg: |- ip access-list NV-SRV-VLAN_ACL 10 permit udp addrgroup LAB1-NTP_ADDR eq ntp 10.10.10.0/24 eq ntp 11 permit tcp addrgroup NV-WAN_ADDR 10.10.10.0/24 portgroup LAB1-SRV-MGMT_PORTS 12 permit udp addrgroup LAB1-NTP-ALLOW eq ntp 10.10.10.4/32 eq ntp 20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.10.0/24 21 permit udp any eq domain 10.10.10.0/24 23 permit tcp any 10.10.10.0/24 established 200 deny ip addrgroup NV-DMZ_ADDR 10.10.10.0/24 300 permit icmp addrgroup NV-WAN_ADDR 10.10.10.0/24 ip access-list NV-DMZ-VLAN_ACL 10 permit udp 10.10.10.4/32 10.10.11.0/24 eq ntp 11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS 20 permit tcp any portgroup NV-SECURE-WEB_PORTS 10.10.11.10/32 21 permit udp any eq domain 10.10.11.0/24 22 permit tcp any 10.10.11.0/24 established 50 permit udp addrgroup DKR-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup NV-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS 51 permit tcp addrgroup DKR-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup NV-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS 300 permit icmp addrgroup NV-MGMT_ADDR 10.10.11.0/24 ip access-list LAB1-DOCKER-MC_PORTS 10 permit udp 10.10.10.4/32 10.10.12.0/24 eq ntp 11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS 20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.12.10/32 21 permit udp any eq domain 10.10.12.0/24 22 permit tcp any 10.10.12.0/24 established 50 permit udp addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS 51 permit tcp addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS addrgroup DMZ-WEB-HOSTS_ADDR portgroup LAB1-DOCKER-MC_PORTS TASK [pause] ********************************************************************************************************************************************************************************************************************************************************************************************************** [pause] Confirm that all Access Control Lists and their respective entries are correct... Press CTRL+C to begin abort sequence.: [[ok: [NV-NET-LAB1-CS1] TASK [Creat Object-groups - Address & Port object-groups] ************************************************************************************************************************************************************************************************************************************************************* changed: [NV-NET-LAB1-CS1] TASK [Create Access Control Lists] ************************************************************************************************************************************************************************************************************************************************************************************ changed: [NV-NET-LAB1-CS1] TASK [Save the deployed configuration (running config) to the memory (startup config).] ******************************************************************************************************************************************************************************************************************************* changed: [NV-NET-LAB1-CS1] PLAY RECAP ************************************************************************************************************************************************************************************************************************************************************************************************************ NV-NET-LAB1-CS1 : ok=11 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Results
Current Saved configuration on the NV-NET-LAB1-CS1
Object Groups
NV-NET-LAB1-CS1# sh object-group
Protocol port object-group LAB1-DOCKER-MC_PORTS
10 eq 25565
20 eq 25566
30 eq 25567
40 eq 25568
IPv4 address object-group LAB1-NTP_ADDR
10 10.10.11.0/24
20 10.10.12.0/24
30 host 192.168.255.22
Protocol port object-group LAB1-SECURE-WEB_PORTS
10 eq 443
20 eq 53
30 eq 20
Protocol port object-group LAB1-SRV-MGMT_PORTS
10 eq 22
20 eq 443
30 eq 123
40 eq 53
IPv4 address object-group NV-DKR-HOSTS_ADDR
10 host 10.10.12.10
IPv4 address object-group NV-DMZ_ADDR
10 10.10.11.0/24
20 10.1.11.0/24
IPv4 address object-group NV-MGMT_ADDR
10 10.0.99.0/24
20 10.1.99.0/24
30 host 10.0.0.30
IPv4 address object-group NV-NTP_ADDR
10 host 10.0.8.2
20 host 10.0.8.5
30 host 10.10.10.4
IPv4 address object-group NV-WAN_ADDR
10 10.0.0.0/8
20 host 10.0.0.30
30 192.168.0.0/16
40 172.16.0.0/12
IPv4 address object-group NV-WEB-HOSTS_ADDR
10 host 10.10.11.10
Access Lists
NV-NET-LAB1-CS1# sh ip access-lists
IP access list LAB1-DOCKER-MC_PORTS
10 permit udp 10.10.10.4/32 10.10.12.0/24 eq ntp
11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS
20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.12.10/32
21 permit udp any eq domain 10.10.12.0/24
22 permit tcp any 10.10.12.0/24 established
IP access list NV-DMZ-VLAN_ACL
10 permit udp 10.10.10.4/32 10.10.11.0/24 eq ntp
11 permit tcp addrgroup NV-MGMT_ADDR 10.10.11.0/24 portgroup LAB1-SRV-MGMT_PORTS
21 permit udp any eq domain 10.10.11.0/24
22 permit tcp any 10.10.11.0/24 established
300 permit icmp addrgroup NV-MGMT_ADDR 10.10.11.0/24
IP access list NV-SRV-VLAN_ACL
10 permit udp addrgroup LAB1-NTP_ADDR eq ntp 10.10.10.0/24 eq ntp
11 permit tcp addrgroup NV-WAN_ADDR 10.10.10.0/24 portgroup LAB1-SRV-MGMT_PORTS
20 permit tcp any portgroup LAB1-SECURE-WEB_PORTS 10.10.10.0/24
21 permit udp any eq domain 10.10.10.0/24
23 permit tcp any 10.10.10.0/24 established
200 deny ip addrgroup NV-DMZ_ADDR 10.10.10.0/24
300 permit icmp addrgroup NV-WAN_ADDR 10.10.10.0/24
IP access list NV-SRV-VLAN_ALLOW
10 permit udp 10.0.0.4/32 eq ntp 10.10.10.10/32 eq ntp
11 permit udp 10.0.0.8/32 eq ntp 10.10.10.10/32 eq ntp
12 permit udp 10.10.10.4/32 eq ntp 10.0.8.2/32 eq ntp
13 permit udp 10.10.10.4/32 eq ntp 10.0.8.5/32 eq ntp
14 permit icmp 10.0.0.0 0.255.255.255 10.10.10.0 0.0.0.255
15 permit tcp 10.0.0.0 0.255.255.255 10.10.10.0 0.0.0.255 eq 22
16 permit tcp 192.168.89.0 255.255.255.0 10.10.10.0 0.0.0.255 eq 22
Conclusion
Everything went pretty smoothly hey! Yep well that’s templating for you and it is also something I could have rolled out to 20 or 100 switches if I needed to, or every switch the moment it is provisioned. Ahhhh the power of automation and templating.
Normally, here I would do some other post verification testing. This actually requires me doing host related Ansible commands. Which I am going to make the next stage of the project. As per my project plan, all my post verification was going to come last but I had decided early on that I would just do it in each playbook which is more sensible. However for the amount of post testing that is required to confirm the right level of access as per the ACL’s is applied and working correctly, I will need to make another playbook which does Linux host based tests.
This will be next weeks stage so stay tuned for this and at the end of this next stage it will actually be the conclusion of the project so I am looking forward to wrapping up this project and sharing some final thoughts on the whole process and what is next.
Have a great week everyone and please do let me know any thoughts, questions and ideas as I would really like to hear them!
Leave a Reply
You must be logged in to post a comment.
Recent Comments