One to read: “Interns with toasters: how I taught people about load balancers”
This was automatically posted from my RSS Reader, and may be edited later to add commentary.
One to read: “Interns with toasters: how I taught people about load balancers”
This was automatically posted from my RSS Reader, and may be edited later to add commentary.
One to read: “How to undo (almost) anything with Git | The GitHub Blog”
This was automatically posted from my RSS Reader, and may be edited later to add commentary.
One to read: “Debian & Ubuntu Equivalents of ‘yum whatprovides’”
This was automatically posted from my RSS Reader, and may be edited later to add commentary.
For those of you who are working with #Ansible… Ansible 2.5 is out, and has an unusual documentation change around a key Ansible concept – `with_` loops Where you previously had:
with_dict: "{{ your_fact }}"
or
with_subelements:
- "{{ your_fact }}"
- some_subkey
This now should be written like this:
loop: "{{ lookup('dict', your_fact) }}"
and
loop: "{{ lookup('subelements', your_fact, 'some_subkey') }}"
Fear not, I hear you say, It’s fine, of course the documentation suggests that this is “how it’s always been”…… HA HA HA Nope. This behaviour is new as of 2.5, and needs ansible to be updated to the latest version. As far as I can tell, there’s no way to indicate to Ansible “Oh, BTW, this needs to be running on 2.5 or later”… so I wrote a role that does that for you.
ansible-galaxy install JonTheNiceGuy.version-check
You’re welcome :)
More useful URLs:
https://hackablepodcast.com/#/episodes/and-were-in
If you’ve ever wondered why you’re encouraged to use different passwords on every website, here’s a perfect example. In this episode from Cybersecurity Firm McAfee, a not-very-technical presenter asks a Penetration Tester (someone who is paid to breach a client’s own security to prove where it’s weaknesses are) to show how easy or hard it is to get into his accounts… In the end the tester goes after this presenter’s Dad’s account… and gets into his Amazon account and his Facebook account in only a couple of minutes.
He also explains some things you can do to keep an eye on these things for yourself. In general this is a fantastic podcast to listen to, and I’d strongly suggest you subscribe to it because it’s not too over-the-top, it’s not pitched at the techno-nerds (like me ;) ) it’s just … right.
This post came about after a couple of hours of iterations, so I can’t necessarily quote all the sources I worked from, but I’ll do my best!
In OpenStack (particularly in the “Kilo” release I’m working with), if you have a networking device that will pass traffic on behalf of something else (e.g. Firewall, IDS, Router, Transparent Proxy) you need to tell the virtual NIC that the interface is allowed to pass traffic for other IP addresses, as OpenStack applies by default a “Same Origin” firewall rule to the interface. Defining this in OpenStack is more complex than it could be, because for some reason, you can’t define 0.0.0.0/0 as this allowed address pair, so instead you have to define 0.0.0.0/1 and 128.0.0.0/1.
Here’s how you define those allowed address pairs (note, this assumes you’ve got some scaffolding in place to define things like “network_appliance”):
allowed_address_pairs: "{% if (item.0.network_appliance|default('false')|lower() == 'true') or (item.1.network_appliance|default('false')|lower() == 'true') %}[{'ip_address': '0.0.0.0/1'}, {'ip_address': '128.0.0.0/1'}]{% else %}{{ item.0.allowed_address_pairs|default(omit) }}{% endif %}"
OK, so we’ve defined the allowed address pairs! We can pass traffic across our firewall. But (and there’s always a but), the product I’m working with at the moment has a floating MAC address in a cluster, when you define an HA pair. They have a standard schedule for how each port’s floating MAC is assigned… so here’s what I’ve ended up with (and yes, I know it’s a mess!)
allowed_address_pairs: "{% if (item.0.network_appliance|default('false')|lower() == 'true') or (item.1.network_appliance|default('false')|lower() == 'true') %}[{'ip_address': '0.0.0.0/1'},{'ip_address': '128.0.0.0/1'}{% if item.0.ha is defined and item.0.ha != '' %}{% for vdom in range(0,40, 10) %},{'ip_address': '0.0.0.0/1','mac_address': '{{ item.0.floating_mac_prefix|default(item.0.image.floating_mac_prefix|default(floating_mac_prefix)) }}:{% if item.0.ha.group_id|default(0) < 16 %}0{% endif %}{{ '%0x' | format(item.0.ha.group_id|default(0)|int) }}:{% if vdom+(item.1.interface|default('1')|replace('port', '')|int)-1 < 16 %}0{% endif %}{{ '%0x' | format(vdom+(item.1.interface|default('1')|replace('port', '')|int)-1) }}'}, {'ip_address': '128.0.0.0/1','mac_address': '{{ item.0.floating_mac_prefix|default(item.0.image.floating_mac_prefix|default(floating_mac_prefix)) }}:{% if item.0.ha.group_id|default(0) < 16 %}0{% endif %}{{ '%0x' | format(item.0.ha.group_id|default(0)|int) }}:{% if vdom+(item.1.interface|default('0')|replace('port', '')|int)-1 < 16 %}0{% endif %}{{ '%0x' | format(vdom+(item.1.interface|default('1')|replace('port', '')|int)-1) }}'}{% endfor %}{% endif %}]{% else %}{{ item.0.allowed_address_pairs|default(omit) }}{% endif %}"
Let's break this down a bit. The vendor says that each port gets a standard prefix, (e.g. DE:CA:FB:AD) then the penultimate octet is the "Cluster ID" number in hex, and then the last octet is the sum of the port number (zero-indexed) added to a VDOM number, which increments in 10's. We're only allowed to assign 10 "allowed address pairs" to an interface, so I've got the two originals (which are assigned to "whatever" the defined mac address is of the interface), and four passes around. Other vendors (e.g. this one) do things differently, so I'll probably need to revisit this once I know how the next one (and the next one... etc.) works!
So, we have here a few parts to make that happen.
The penultimate octet, which is the group ID in hex needs to be two hex digits long, and without adding more python modules to our default machines, we can't use a "pad" filter (to add 0's to the beginning of the mac octets), so we do that by hand:
{% if item.0.ha.group_id|default(0) < 16 %}0{% endif %}
And here's how to convert the group ID into a hex number:
{{ '%0x' | format(item.0.ha.group_id|default(0)|int) }}
Then the next octet is the sum of the VDOM and PortID. First we need to loop around the VDOMs. We don't always know whether we're going to be adding VDOMs until after deployment has started, so here we will assume we've got 3 VDOMs (plus VDOM "0" for management) as it doesn't really matter if we don't end up using them. We create the vdom variable like this:
{% for vdom in range(0, 40, 10) %} STUFF {% endfor %}
We need to put the actual port ID in there too. As we're using a with_subelement loop we can't create an increment, but what we can do is ensure we're recording the interface number. This only works here because the vendor has a sequential port number (port1, port2, etc). We'll need to experiment further with other vendors! So, here's how we're doing this. We already know how to create a hex number, but we do need to use some other Jinja2 filters here:
{{ '%0x' | format(vdom+(item.1.interface|default('1')|replace('port', '')|int)-1) }}
Let's pull this apart a bit further. item.1.interface
is the name of the interface, and if it doesn't exist (using the |default('1')
part) we replace it with the string "1". So, let's replace that variable with a "normal" value.
{{ '%0x' | format(vdom+("port1"|replace('port', '')|int)-1) }}
Next, we need to remove the word "port" from the string "port1" to make it just "1", so we use the replace filter to strip part of that value out. Let's do that:
{{ '%0x' | format(vdom+("1"|int)-1) }}
After that, we need to turn the string "1" into the literal number 1:
{{ '%0x' | format(vdom+1-1) }}
We loop through vdom several times, but let's pick one instance of that at random - 30 (the fourth iteration of the vdom for-loop):
{{ '%0x' | format(30+1-1) }}
And then we resolve the maths:
{{ '%0x' | format(30) }}
And then the |format(30)
turns the '%0x'
into the value "1e"
Assuming the vendor prefix is, as I mentioned, 'de:ca:fb:ad:' and the cluster ID is 0, this gives us the following resulting allowed address pairs:
[
{"ip_address": "0.0.0.0/1"},
{"ip_address": "128.0.0.0/1"},
{"ip_address": "0.0.0.0/1", "mac_address": "de:ca:fb:ad:00:00"},
{"ip_address": "128.0.0.0/1", "mac_address": "de:ca:fb:ad:00:00"},
{"ip_address": "0.0.0.0/1", "mac_address": "de:ca:fb:ad:00:0a"},
{"ip_address": "128.0.0.0/1", "mac_address": "de:ca:fb:ad:00:0a"},
{"ip_address": "0.0.0.0/1", "mac_address": "de:ca:fb:ad:00:14"},
{"ip_address": "128.0.0.0/1", "mac_address": "de:ca:fb:ad:00:14"},
{"ip_address": "0.0.0.0/1", "mac_address": "de:ca:fb:ad:00:1e"},
{"ip_address": "128.0.0.0/1", "mac_address": "de:ca:fb:ad:00:1e"}
]
I hope this has helped you!
Sources of information:
In my day job, I’m using Ansible to provision networks in OpenStack. One of the complaints I’ve had about the way I now define them is that the person implementing the network has to spell out all the network elements – the subnet size, DHCP pool, the addresses of the firewalls and names of those items. This works for a manual implementation process, but is seriously broken when you try to hand that over to someone else to implement. Most people just want something which says “Here is the network I want to implement – 192.0.2.0/24″… and let the system make it for you.
So, I wrote some code to make that happen. It’s not perfect, and it’s not what’s in production (we have lots more things I need to add for that!) but it should do OK with an IPv4 network.
Hope this makes sense!
---
- hosts: localhost
vars:
- networks:
# Defined as a subnet with specific router and firewall addressing
external:
subnet: "192.0.2.0/24"
firewall: "192.0.2.1"
router: "192.0.2.254"
# Defined as an IP address and CIDR prefix, rather than a proper network address and CIDR prefix
internal_1:
subnet: "198.51.100.64/24"
# A valid smaller network and CIDR prefix
internal_2:
subnet: "203.0.113.0/27"
# A tiny CIDR network
internal_3:
subnet: "203.0.113.64/30"
# These two CIDR networks are unusable for this environment
internal_4:
subnet: "203.0.113.128/31"
internal_5:
subnet: "203.0.113.192/32"
# A massive CIDR network
internal_6:
subnet: "10.0.0.0/8"
tasks:
# Based on https://stackoverflow.com/a/47631963/5738 with serious help from mgedmin and apollo13 via #ansible on Freenode
- name: Add router and firewall addressing for CIDR prefixes < 30
set_fact:
networks: >
{{ networks | default({}) | combine(
{item.key: {
'subnet': item.value.subnet | ipv4('network'),
'router': item.value.router | default((( item.value.subnet | ipv4('network') | ipv4('int') ) + 1) | ipv4),
'firewall': item.value.firewall | default((( item.value.subnet | ipv4('broadcast') | ipv4('int') ) - 1) | ipv4),
'dhcp_start': item.value.dhcp_start | default((( item.value.subnet | ipv4('network') | ipv4('int') ) + 2) | ipv4),
'dhcp_end': item.value.dhcp_end | default((( item.value.subnet | ipv4('broadcast') | ipv4('int') ) - 2) | ipv4)
}
}) }}
with_dict: "{{ networks }}"
when: item.value.subnet | ipv4('prefix') < 30
- name: Add router and firewall addressing for CIDR prefixes = 30
set_fact:
networks: >
{{ networks | default({}) | combine(
{item.key: {
'subnet': item.value.subnet | ipv4('network'),
'router': item.value.router | default((( item.value.subnet | ipv4('network') | ipv4('int') ) + 1) | ipv4),
'firewall': item.value.firewall | default((( item.value.subnet | ipv4('broadcast') | ipv4('int') ) - 1) | ipv4)
}
}) }}
with_dict: "{{ networks }}"
when: item.value.subnet | ipv4('prefix') == 30
- debug:
var: networks
One to read: Top 15 Docker Commands – Docker Commands Tutorial
This is more of a reminder to myself, but I’m starting to do some work with some Docker containers, and struggled to work out how to get into a running container. This post basically gave me the one-liner I needed:
docker exec -it [container id|name] bash
Sometimes, it’s inevitable (maybe? :) ), you’ll add a git submodule from the wrong URL… I mean, EVERYONE’S done that, right? … right? you lot over there, am I right?… SIGH.
In my case, I’m trying to make sure I always use the https URLs with my github repo, but sometimes I add the git URL instead. When you run git remote -v
in the path, you’ll get something like:
origin git@github.com:your-org/your-repo.git (fetch)
instead of
origin https://github.com/your-org/your-repo (fetch)
which means that when someone tries to clone your repo, they’ll be being asked for access to their public keys for all the submodules. Not great
Anyway, it should be easy enough – git creates a .gitmodules file in the repo root, so you should just be able to edit that file, and replace the git@
with https://
and the com:
with com/
… but what do you do next?
Thanks to this great Stack Overflow answer, I found you can just run these two commands after you’ve made that edit:
git submodule sync ; git submodule update --init --recursive --remote
Isn’t Stack Overflow great?
Over the past few days I’ve been binge listening to the Arrested Devops podcast. In one of the recent episodes (“Career Change Into DevOps With Michael Hedgpeth, Annie Hedgpeth, And Megan Bohl (ADO102)“) one of the interviewees mentions that she got started in DevOps by using Inspec.
Essentially, inspec is a way of explaining “this is what my server must look like”, so you can then test these statements against a built machine… effectively letting you unit test your provisioning scripts.
I’ve already built a fair bit of my current personal project using Ansible, so I wasn’t exactly keen to re-write everything from scratch, but it did make me think that maybe I should have a common set of tests to see how close my server was to the hardening “Benchmark” guides from CIS… and that’s pretty easy to script in inspec, particularly as the tests in those documents list the “how to test” and “how to remediate” commands to execute.
These are in the process of being drawn up (so far, all I have is an inspec test saying “confirm you’re running on Ubuntu 16.04″… not very complex!!) but, from the looks of things, the following playbook would work relatively well!