“Sudo Mastery” print + ebook bundle via Amazon

I’ve mentioned this before in various forums and in passing, but it bears a small emphasis.

Some people want books in both ebook and print. I’m not set up to do that, but Amazon is making that happen through their Matchbook program. The general idea is that if you bought a book in paper, you can get the ebook version at a steep discount.

I’ve put both existing Mastery paperbacks in the program. If you’ve bought the print book from Amazon, you can get the electronic version for $2.99. When Sudo Mastery hits paperback, it’ll be included.

Why $2.99?

I feel the fair price for the combo is about $20. The list price on the print books is $20, but Amazon knocks a few bucks off based on their own inscrutable algorithms. I’ve seen SSH Mastery as low as $14 and as high as $18.

There’s also the Amazon royalty on Kindle books. Ebooks priced less than $2.99 pay me a 35% royalty. Ebooks priced at $2.99 and up pay 70% royalties. If I price the Matchbook versions at $2.99, I make about $2.00 per sale. If I price them at $1.99 (the next lower option), I make about $0.66/sale. Ouch. Either way, that’s a lot of sales to pay the mortgage.

All this is a long-winded way of saying:

If you want both the print and ebook versions of Sudo Mastery, wait until the print version comes out. You’ll be able to get both for about $20, more or less.

I never buy my print books through Amazon’s retail channel — I buy them in bulk, from their CreateSpace arm. I would really like confirmation that folks who bought a print Mastery book from Amazon can get the ebook at a discount. If you bought a print Mastery, please take a look at Amazon. See if you can get the Matchbook deal and let me know in the comments here.

“Sudo Mastery” ebook widely available, and acknowledgements

At long last, Sudo Mastery is now available in ebook form on most platforms.

You can get it at my bookstore or Amazon.

It’s also available at Smashwords, but Smashwords doesn’t support footnotes. They do support a workaround that puts all footnotes together at the end of a chapter or the end of the book, but it’ll take some work on my part to make that happen.

It’s not at Barnes & Noble yet, because their new Nook Press application completely mangled the book’s formatting. As I sell an average of one book a month through B&N, I’m seriously considering not having the book there.

Print will come some time in November.

I appreciate all the people who helped me write this book. So, in that spirit, here are the acknowledgements.

I want to thank the folks who reviewed the manuscript for Sudo Mastery before publication: Bryan Irvine, JR Aquino, Hugh Brown, and Avigdor Finkelstein. Special thanks are due to Todd Miller, the current primary developer of sudo, who was very patient and helpful when answering my daft questions.

While I appreciate my technical reviewers, no errors in this book are their fault. All errors are my responsibility. Mine, do you hear me? You reviewers want blame for errors? Go make your own.

XKCD fans should note that the author does not particularly enjoy sandwiches. However, Miod Vallat, currently exiled to France, would really like a sandwich with nice fresh bread, really good mustard, and low-carb ground glass and rusty nails. And Bryan Irvine would like a rueben.

This book was written while listening obsessively to Assemblage 23.

Now, to finish writing my big 2013 fiction project before the end of the year…

checking group membership in Ansible templates

I use SolusVM as a virtualization solution, mainly because it’s pretty cheap and mostly effective. The new web-managed migration feature requires that the master node have SSH access into the slave nodes. As root. (Insert lots of swearing here.)

This isn’t a problem, except that I centrally manage my OpenSSH configuration with Ansible. I don’t want all of my hosts to permit the master SolusVM node to log in as root; I only want the Solus hosts to get that setting, and even then only from the master node’s IP address.

The good news is, I have an Ansible group defined for SolusVM hosts. I must modify my template so that it checks if the host is in this group. Ansible provides two variables for the host name, ansible_fqdn (fully qualified name) and ansible_hostname (short hostname). Use the one that reflects your inventory file.

{% if ansible_fqdn in groups['solus-hw'] %}
#solus needs root login from master node
Match Address 192.0.2.12
PermitRootLogin without-password
{% endif %}

As this is a Match statement, it goes at the end of your configuration. My complete sshd_config.j2 looks like this:

#{{ ansible_managed }}
Protocol 2
ListenAddress {{ ansible_ssh_host }}
X11Forwarding yes
PubkeyAuthentication yes
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
{% if ansible_system == 'OpenBSD' %}
#no PAM on OpenBSD
{% else %}
UsePAM yes
{% endif %}
Subsystem sftp {{ program_sftp_server }}
{% if ansible_fqdn in groups['solus'] %}
#solus needs root login from master node
Match Address 192.0.2.12
PermitRootLogin without-password
{% endif %}

Do a push, and done. On to the next problem!

Sudo Mastery off to copyeditor

I just shipped the tech-reviewed copy of Sudo Mastery off to the copyeditor. She’ll have it back to me in a few days, and the book will move into production immediately thereafter.

This means that the pre-order discount will expire soon. How soon is soon? It’s soon.

Now I’m off to work on one of my other 2013 goals. Thanks to my appendix’s untimely detonation at the beginning of the year and my Europe trip I won’t accomplish everything on that list, but that’s no reason to not get as many of them finished as possible.

EuroBSDCon, and Sudo Mastery

How’s that for diverse topics in one post?

I just got back from EuroBSDCon 2013 on Malta. The EuroBSDCon Foundation and Andre Opperman did a great job with the conference, and presented one of the best sets of talks and keynotes and related programs I’ve seen in years. It’s motivated me to try to improve BSDCan, but I’ll babble about that in another post.

I followed EuroBSDCon with a few days in Paris, to talk to other authors, network, and figure out some “business of writing” stuff. Plus see the Eiffel Tower and the catacombs, of course.

Now that I’m home, I’m diving into the technical reviews of Sudo Mastery.

Normally when I have a book out for tech review, I post a variety of reminders during the time people should be reviewing. “Two weeks to get comments back to me!” “One week!” “Six hours and three minutes!” I didn’t do that this time, instead focusing on things like distributing blacklists via BGP and automated deployment of FreeBSD and Bhyve and relayd and and and and…

In a weird coincidence, I haven’t received as many tech reviews as I usually do.

Why do people nag? Because it works.

If you’re one of the folks who volunteered to review the manuscript, you have a couple days left to send me comments. I would really like to get the book to the copyeditor by next Monday.

“Sudo Mastery” tech reviewers wanted

Thursday night, I finished the first draft of Sudo Mastery. Today, I went through the manuscript, removed my known tics, discovered a few more tics that needed killing, cleaned up bits and pieces, and now have a work ready for technical review.

Which is where you lot come in. I’m looking for people with sudo experience to read the book and tell me where I’ve screwed up. My screw-ups usually come in two flavors:

1) I’m technically wrong.
2) I use something in a way other people don’t
3) I don’t include something important, because I’ve never used it.

The goal of Sudo Mastery is not to get 100% of my readers to 100% sudo expertise, but instead to get 90% of my readers everything they will need. The remaining 10% will get a solid grounding in sudo and pointers on solving their particularly pernicious edge cases. The idea is roughly similar to my other Mastery books or Cisco Routers for the Desperate.

The contents of Sudo Mastery are:

  1. Introduction
  2. sudo and sudoers
  3. editing and testing sudoers
  4. lists and aliases
  5. options and defaults
  6. shell escapes, editors, and sudoers policies
  7. configuring sudo
  8. user environments versus sudo
  9. sudo for intrusion detection
  10. sudoers distribution and complex policies
  11. security policies in ldap
  12. logging & debugging
  13. authentication

Most of these chapters are short. And much of the writing needs rewriting. But there’s no point in rewriting until I know the content is technically correct.

If you know sudo, if you consider yourself a sudo master already, this is your chance to spread your wisdom. Read my general notes for tech reviewers, and email me at mwlucas at michael w lucas dotcom. (The W is vastly important… you might get a response from the domain without one, but it won’t be what you expected.)

I plan to send out manuscripts over the next week. I’m asking for people to return their comments on or before 5 October. I plan to revise the manuscript the week of 6 October and get it to the copyeditor before the 15th.

With anything resembling luck, the completed book will be available before Thanksgiving. I’d really like to have the holidays off this year.

First draft of “Sudo Mastery” complete

I just typed the last words of the first draft of Sudo Mastery.

The completed first draft is available for early purchasers. As it’s no longer an incomplete draft, I’ve raised the early purchase price to $8.99. That’s more than the really early buyers paid, but less than the final price. (Selling the early drafts from my own bookstore lets me experiment, so I’m ratcheting up the price to see what happens.)

What happens now?

First, I take a couple days and do something else. Anything else. This is vital, as I need some distance from the manuscript. I know it’s a big steaming pile of bodily waste, sure. But I need to be able to see the details of how, exactly, that pile is arranged.

Then: go over the manuscript from beginning to end, looking for obvious technical and writing problems.

Then spellcheck the book. (The purpose of an as-you-type spellchecker is to slow down the writing process. Note that a grammar checker never enters into this process.)

Then solicit technical reviewers. (Don’t volunteer yet: if you do, I’ll put you on my list of people who can’t follow directions.)

Then I go to EuroBSDCon. When I return, I integrate the comments into the book in another round of testing and fact-checking and rewriting.

Off to copyeditor.

Fix what the copyeditor finds.

Then the book comes out.

I’m not writing an Ansible book…

…at least not now.

This is a “post it now so I can point to it later” piece.

I met Michael DeHaan, the Ansible creator, primary author, lead, and probably Grand Poobah, at AnsibleFest in Boston. We discussed the possibility of an Ansible book. He’s certainly open to the idea.

But we agreed that Ansible is moving too dang quickly to document in a book. By the time I finished a book, progress in Ansible would make the book obsolete. Ansible development will slow down at some time, making a book much more realistic.

I’m also not convinced that I’m the right person to write this book. I use a narrow slice of Ansible features, and other folks use a much greater set of Ansible features. My use is expanding, but still, I’m more likely to write a series of small Ansible books that reflect my growing understanding as opposed to one massive tome.

I’ll continue to document what I learn, and we’ll see what the future holds.

Cross-platform OpenSSH server management with Ansible

I’ve previously written about managing the OpenSSH server with Ansible. That example focused on my BSD servers. I also manage Ubuntu and CentOS machines as well as my FreeBSD and OpenBSD. While the BSD machines are very similar, Ubuntu and CentOS might are two different operating systems. Can I manage all of them by hand? Sure. But some of these call their SSH service ssh, while others call it sshd. They store their SFTP servers in different directories.

I want to manage all of these simultaneously. With one script. When I need to change my sshd configuration, I want the change to propagate across the network, to all of my machines, simultaneously.

Ansible makes it pretty easy to write a single sshd configuration that works on all systems, through templates. A template is a Jinja2 file that fills in variables. Jinja2 is closely related to Python, but it isn’t Python.

The first thing you must do is decide what how you want your SSH server configured. Most operating systems ship a very inclusive example sshd_config, with most options commented out out. But to see how your SSH server is actually configured, run sshd -T.

# sshd -T
port 22
protocol 2
addressfamily any
listenaddress [::]:22
listenaddress 0.0.0.0:22
usepam 1
serverkeybits 1024
logingracetime 120
keyregenerationinterval 3600
x11displayoffset 10

This prints out the running sshd configuration, with all the options as the running instance uses them.

Generate this configuration across your operating systems. Compare them. Which settings do you really want? What differences don’t matter in your environment? Which differences are set in your server by default? (If you aren’t familiar with the innards of OpenSSH configuration, permit me to suggest a book on the subject.)

Some operating systems set their default sshd_config options differently. For example, FreeBSD’s default is UsePam yes. CentOS and Ubuntu default to UsePam no. I’m safest if I specify UsePAM in my configuration file, to avoid ambiguity.

But OpenBSD doesn’t use PAM. If I use that configuration item in an sshd_config for that machine, then sshd won’t run.

Similarly, there’s the location of the sftp-server program. Different operating systems store these helper programs in different locations.

The way to handle all of these is to use a group variables, and assign the things that change a variable. Here’s my new cross-platform template for sshd_config.

#{{ ansible_managed }}
Protocol 2
ListenAddress {{ ansible_ssh_host }}
X11Forwarding yes

PubkeyAuthentication yes
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
{% if ansible_system == 'OpenBSD' %}
#no PAM on OpenBSD
{% else %}
UsePAM yes
{% endif %}

Subsystem sftp {{ program_sftp_server }}

Each per-operating-system group file defines the variable program_sftp_server. Ansible automatically populates the variable ansible_system with the operating system name. And ansible_ssh_host comes from a per-host variable.

I would rather use something like “unless ansible_system=’OpenBSD'” to print UsePAM in the configuration file for the operating systems that need it, but that doesn’t seem to be an option in Jinja2.

The playbook changes a little as well:

---
- hosts: managed-sshd
user: ansible
sudo: yes

tasks:
- name: create key directory
action: file path=/etc/ssh/authorized_keys state=directory
owner=0 group=0 mode=0755

- name: upload user key
action: copy src=/home/ansible/crossplatform/etc/ssh/authorized_keys/{{ item }}
dest=/etc/ssh/authorized_keys/
owner=0 group=0 mode=644
with_items: sshusers

- name: sshd configuration file update
template: src=/home/ansible/crossplatform/etc/ssh/common-sshd_config.j2
dest=/etc/ssh/sshd_config
backup=yes
owner=0 group=0 mode=0644
validate='/usr/sbin/sshd -T -f %s'
notify:
- restart sshd

handlers:
- name: restart sshd
service: name={{ service_sshd }} state=restarted

The key difference here is that I’ve defined a per-OS variable, service_sshd. The operating systems can call their sshd servers sshd, ssh, or derangedweasel for all I care. I figure it out once, set it in the variable file, and get on with my life.

managing sshd with Ansible

My environment has two common tasks when managing OpenSSH servers: copying user’s authorized_keys files to the server, and changing the sshd configuration file /etc/ssh/sshd_config. I use Ansible for both, using a single playbook. Running the playbook updates all the authorized_keys files on every host and verifies that sshd is properly configured. (Not that any of my minions would reconfigure sshd without going through change control, or anything like that.)

I’ll start with authorized_keys management.

My servers are grouped by functions. Not all users have access to all servers, and I only want to copy a user’s authorized_keys file to a server if that user can access that server. Start by creating a group_vars directory at the top level of your Ansible install. Create a file for each group that you manage authorized_keys on. Within that file, create a YAML list of all users who can access your server. For example, my DNS servers are all in the Ansible group “dns.” I’ve created the file /etc/ansible/group_vars/dns, and it contains:

---

#users who get SSH access to these machines
sshusers:
  - mwlucas
  - ansible
  - john
  - harry
  - mandy

I have a similar file for my LDAP servers, in the group “ldap.” The file is /etc/ansible/group_vars/ldap.

---

#users who get SSH access to these machines
sshusers:
  - mwlucas
  - ansible
  - jake
  - harry

I think you see the trend here. Remember, YAML lists are space-sensitive, and tabs are forbidden.

You cannot define these lists in /etc/ansible/hosts: you must use a variables file.

Then there’s the SSH configuration part. I run an artisan network. (“Artisan network” means “each server is set up uniquely, managed uniquely, and has little in common with anything else, really frustrating any hopes of easy or consistent systems administration.” While I’m slowly dragging everything towards mass manageability, it’s not there yet.) Each host has multiple IP addresses. I want sshd to only listen on a single IP address. I can’t consistently pull the IP from the host itself, so I need to hard-code it in Ansible.

Fortunately, Ansible should already have this information. A host’s ssh daemon probably isn’t listening on the IP address that DNS gives for the host — that is, the IP referred to by the hostname ldap.michaelwlucas.com probably doesn’t have an SSH server on it. It might be ldap-ssh or ldap-mgmt or ldap-pleasekillmenow. So I’ve had to tell Ansible the address to connect to.

The directory /etc/ansible/host_vars contains a file for each host. The file /etc/ansible/host_vars/ldap.michaelwlucas.com contains something like this:

---

ansible_ssh_host: 192.0.2.88

The ansible_ssh_host is a dedicated Ansible variable, giving the hostname or IP address that Ansible should use to SSH into this host. I’m going to piggyback this for my SSH configuration file. (If you use hostnames in your ansible_ssh_host, you’ll need to either create a separate variable with the IP or to convert the hostname to an IP address somewhere in this process. If you figure out an easy way to do this, do let me know.)

So, how do I want sshd configured?

As I’m mass-managing these machines’ sshd service, I neither need nor want all the various default options in sshd_config. Having those options in the configuration file is great when you’re manually configuring sshd, but I don’t want anyone on any of these servers to change the sshd configuration without going through change control.

So, let’s go to a machine that has a properly configured sshd and get the non-default options.

$ grep -v ^# /etc/ssh/sshd_config > sshd_config.j2

The .j2 indicates this is a Jinja2 template, which is what Ansible uses for creating files. Look at what’s left after I remove the blank lines.

ListenAddress 192.0.2.88
PermitRootLogin without-password
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
Subsystem sftp /usr/libexec/sftp-server

The only problem here is the ListenAddress setting, which needs to be different on each host. I make that a variable. I also add a revision tag and an Ansible management statement.

#$Id$
#{{ ansible_managed }}
ListenAddress {{ ansible_ssh_host }}
PermitRootLogin without-password
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
Subsystem sftp /usr/libexec/sftp-server

The {{ ansible_ssh_host }} is a variable statement, pulling in that variable from Ansible itself.

So, how to use all this in a playbook?

---
- hosts: freebsd
  user: ansible
  sudo: yes

  tasks:
  - name: create key directory
    action: file path=/etc/ssh/authorized_keys state=directory
      owner=0 group=0 mode=0755

  - name: upload user key
    action: copy src=/home/ansible/crossplatform/etc/ssh/authorized_keys/{{ item }}
      dest=/etc/ssh/authorized_keys/
      owner=0 group=0 mode=644
    with_items: sshusers

  - name: sshd configuration file update
    template: src=/etc/ansible/configs/etc/ssh/sshd_config.j2
      dest=/etc/ssh/sshd_config
      backup=yes
      owner=0 group=0 mode=0644
      validate='/usr/sbin/sshd -T -f %s'
    notify:
    - restart sshd

  handlers:
    - name: restart sshd
      service: name=sshd state=restarted

The first task creates the directory for key storage. I do not allow users to upload authorized_keys files for their own account. We don’t want an intruder to add their own key to a user account. Instead, each user’s authorized keys are in a file named after the username, in the directory /etc/ssh/authorized_keys, and our sshd_config tells sshd to look in that directory.

The second task copies the user keys listed in the sshusers variable defined in the group_vars file. While Ansible has an authorized_keys module specifically for handling these files, it has problems with quotes in restricted keys. Until that’s fixed, I’ll fall back to the perfectly adequate “copy” module.

The third task reads the jinja2 template for sshd_config, adds the necessary information, and copies the file to the server. It also validates that the configuration is legitimate — not that it will do what you want, mind you, but it will verify that sshd understands this sshd_config file.

Last, we restart sshd.

My next steps with this will be to update the template so it works on non-FreeBSD hosts. But that’ll be a topic for another day.

Want more on configuring SSH? Check out my book SSH Mastery.