1Password AWS Credentials

1Password CLI 2 offers a way to use AWS credentials with the AWS CLI. However, you have to create shell aliases for all the commands you want to use with with. A better solution would be to let the AWS CLI/SDK lookup the credentials itself in 1Password. Luckily, the AWS config supports this with credential_process!

Requirements to make this work:

  • AWS CLI v2. It might work with v1 but I did not test it.
  • 1Password items with these fields:
    • access key id or access_key_id
    • secret access key or secret_access_key
  • 1Password CLI 2 which can authenticate to your vault.
  • My op-aws-credentials.py script in a conveinent location (/usr/local/bin).

When you have those things in place, you can modify your ~/.aws/config file:

[default]
credential_process = /opt/local/bin/op-aws-credentials.py --vault AWS --item default

[profile1]
credential_process = /opt/local/bin/op-aws-credentials.py --vault AWS --item profile1

[profiloe2]
credential_process = /opt/local/bin/op-aws-credentials.py --vault AWS --item profile2

Then delete the lines from your ~/.aws/credentials file. This example assume you have a vault called “AWS” with items in it “default”, “profile1”, and “profile2”.

I’ve tested this with the AWS CLI v2, terraform 1.2.2, and some boto3 scripts (which is 95% of my CLI usage).

Gentoo Gateway/Router, Comcast, & IPv6

After many attempts and stumbles, I’ve finally been able to get my home network setup with IPv6 on Comcast. Many thanks to this Native IPv6 on Comcast article; I used it as my starting point to building the network I wanted.

The Goal

My IPv4 setup is your standard NAT where the gateway machine holds the external IP. Comcast will give you an IPv6 prefix, and I wanted to use that for the internal network instead of NAT. The goal is to have an IPv6 address bound to my external interface, and an IPv6 prefix advertised on my internal network for stateless configuration.

The Setup

Before I get into the details, I have a few notes to share about my setup.

Gentoo Linux: the Linux for tinkerers, which also means this was all a little more difficult than it should have been (but that’s the way I like it). If you’re using another distribution then you won’t be able to follow this guide step-by-step. But hopefully parts of it will still be useful.

Interface Names: my instructions and scripts will refer to two interface names: ethwan and brint. My external interface is ethwan and is a physical device. My internal interface is brint (for bridged internal) and is a bridge of my two internal interfaces (ethwlan and ethlan). You’ll have to change the scripts to match your interface names.

iproute2: Many of the scripts assume you’re using the iproute2 package.

Kernel 3.14: I spent a lot of time scratching my head because directions I found on the internet just weren’t working. I think it’s because of subtle changes in the 3.x kernel that made advice for 2.6 kernels obsolete. YMMV.

Getting the IPv6 Address

First thing we need to do is get an IPv6 address allocated to ethwan. For the longest time I’ve used the dhcpcd package, but I found dhclient worked much better for IPv6. Unfortunately, Gentoo doesn’t provide the right netifrc modules to support both DHCPv4 and DHCPv6 on the same interface. Starting with the existing dhclient module, I modified it to support DHCPv6: /lib64/netifrc/net/dhclientv6.sh

Now setup your /etc/conf.d/net configuration to use both:

modules="dhclient
         dhclientv6"

config_ethwan="dhcp
               dhcpv6"
dhcp_ethwan="release nodns nontp nonis nosendhost"
dhcpv6_ethwan="release nodns nontp nonis nosendhost"
dhclientv6_ethwan="-P"
rc_net_ethwan_after="net.brint"

Couple notes:

  • I run my own DNS, hence the “nodns”. You might not want this option.
  • Ibid with NTP: “nontp”.
  • The argument for dhclientv6 of “-P” tells it to request the prefix we’ll use for the internal network.
  • The scripts need brint started, so start ethwan after it.
  • For a long time, my MTU was set to 576. This is far to small for IPv6, and you’ll get a cryptic error when dhclient tries to assign an address. Increase it to 1500.

Restarting ethwan should now give you an IPv6 address! Launch a browser on the gateway and test it.

Assigning the Prefix

Having the prefix isn’t enough. You also have to setup forwarding in the kernel, accept router announcements from the cable modem, add a routing table entry, and start advertising the prefix to the internal network.

Complicating things is that the prefix could change at any time. But there is a way to automate all the configuration changes and make sure the internal network always stays current.

Next we’re going to write a hook for dhclient that runs whenever our prefix changes and adjusted the settings appropriately. It does this by looking at environment variables that dhclient passes to dhclient-script, which will then call our script.

The packaged dhclient-script will check for /etc/dhcp/dhclient-exit-hooks and sources that. Lots of distributions provide the /etc script, but not Gentoo. No worries, it’s easy to write our own dhclient-exit-hooks:

#!/bin/bash

if [ -d /etc/dhcp/dhclient-exit-hooks.d ]; then
	for f in /etc/dhcp/dhclient-exit-hooks.d/*.sh ; do
		if [ -x ${f} ]; then
			"${f}"
		fi
	done
fi

Important: my version of this script executes the hooks in /etc/dhcp/dhclient-exit-hooks.d. I did this so that I could use bash in my script, and not deal with the abomination that is sh syntax. If your distribution sources the individual hooks, then you’ll have to either adjust your dhclient-exit-hooks script, or adjust the next script that sets up the prefix.

The main script is /etc/dhcp/dhclient-exit-hooks.d/ipv6.sh. At the very least you will need to adjust the variables at the start. A couple notes:

  • It starts, stops, and checks radvd by running /etc/init.d/radvd. Other systems manage services other ways; adjust accordingly.
  • This will only run radvd when it thinks a prefix is defined. You should not set radvd to start with your system; the script will completely manage it.
  • It turns IPv6 forwarding on for all interfaces, and accept_ra to “2” for ethwan. I’ve found that on a 3.14 kernel, forwarding only works if enabled this way. If you don’t want forwarding for all interfaces, you can use iptables to block it.

Configuring radvd

The ipv6.sh script looks for a template file in the same directory as the configuration: radvd.conf.template. It will replace “__PREFIX__” with the DHCPv6 assigned prefix. Mine contains this:

interface brint {
        AdvSendAdvert on;
        MaxRtrAdvInterval 300;

        RDNSS fd11:2233:4455:1::1 { };
        DNSSL example.org { };

        prefix __PREFIX__ {
                AdvOnLink on;
                AdvAutonomous on;
        };

        prefix fd11:2233:4455:1::/64 {
                AdvOnLink on;
                AdvAutonomous on;
        };
};

This is setup for stateless configuration, which is the only way OS X and iOS devices work. If you’d like to use DHCPd then the process would be similar.

I’ve also registered a Unique Local Address prefix for my internal network. No, it’s not “fd11:2233:4455::”. I use this for contacting internal services on IPv6 since the Comcast prefix isn’t stable. The “prefix” line for this space advertises it to the internal network, for stateless configuration.

Since I run my own internal DNS, I put the IPv6 address of the server in my radvd.conf. If you don’t have one then leave the RDNSS and DNSSL lines out.

Also configure radvd to not touch the kernel forwarding setting since we do this in the ipv6.sh script. On Gentoo, it’s in /etc/conf.d/radvd, setting: FORWARD=”no”

Troubleshoot

Within a short period your internal devices should get an IPv6 address. With Comcast, the address assigned to ethwan and the prefix we advertise on brint are entirely different. No worries; this is expected!

You might also notice that unlike previous instructions, brint does not have an address allocated from the prefix. This is not required, all it needs is the routing table entry. Routing of packets from internal devices to the internet happens using the link-local addresses.

On an internal device, test your connection again. You can also use ping6 to contact “www.kame.net” or “ipv6.google.com”.

Internal devices are not getting an address. Make sure IPv6 is enabled on your device and set for automatic configuration. Check the logs for radvd and make sure it is running and advertising the right prefix on the right interface. You can run it in debug mode as: “radvd -d 5 -m stderr”.

Internal devices have an address but can’t contact services. Make sure the routing entry is set on brint. Make sure forwarding is set on all interfaces: “sysctl net.ipv6.conf.all.forwarding”.

Gateway gets an address but can’t contact services. Make sure that it is accepting routing advertisements on ethwan: “sysctl net.ipv6.conf.ethwan.accept_ra”. This must be set to “2” because Linux will ignore a setting of “1” if forwarding is also set. Check that the routing table has as its default IPv6 route the link-local address of the cable modem (should happen automatically).

WP Shibboleth

For our implementation of WordPress Shibboleth we’ve made a couple modifications to the stock plugin. This comes in two flavors, a patch to the plugin and a seperate plugin that adds additional functionality:

WP Shibboleth

The patch adds these features, you will want to search for “XXX: UIUC” to customize to your setup:

  • Login form message that allows people to choose Shibboleth or local authentication in WordPress
  • Add a “redirect_to” parameter to the Shibboleth login process, so that WordPress can return properly after authentication.

UIUC Shibboleth

The UIUC Shibboleth plugin extends the stock Shibboleth plugin by auto-creating users from LDAP. When we configured our Shibboleth setup we chose to use the eduPersonPrincipalName as the username in WordPress. But this does not mix well with how WordPress expects to add new users from the “site-new.php” and “user-new.php” pages. We replace the “get_user_by” function to create users in WordPress with the eduPersonPrincipalName when a new user or site is added to the system and the Shibboleth user didn’t already exist.

To use the UIUC Shibboleth plugin you will need an LDAP directory that contains all the users Shibboleth with authenticate. Users in the directory will need these attributes:

  • LDAP Username: the unscoped username; we use “uid”. This could be “samaccountname” for Active Directory.
  • LDAP Email: most likely “mail”.
  • Shibboleth Username: what the WP Shibboleth plugin will consider the username, as retrieved from LDAP. Our directory has “edupersonprincipalname” (or eppn) as this attribute. What you place here will depend on how you’ve configured your WP Shibboleth plugin.
  • Shibboleth Email: what the WP Shibboleth plugin will consider the email address, as retrieved from LDAP. Our directory uses the same attribute in both cases: “mail”.

Note that all the attribute names are lowercased.

WP Domain Mapping

Disclaimer: not all of the features we’ve added are in active use. Use with caution and test heavily! If you have a question or comment, feel free to email me.

For our implementation we’ve added some extensions to the stock WordPress Domain Mapping plugin. Our patch is hosted here: dm-v0.5.4.3-uiuc.patch. After applying, search for “XXX: UIUC” to see specific changes we’ve added that you might want to adapt to your install. Here is a summary of the things we’ve modified:

Per-Blog Enable

We wanted the ability to enable domain mapping on a per-blog basis. When this setting is enabled (by a super admin) the domain mapping does not appear on a blog’s Setting page unless a super admin has enabled it for that particular blog.

Per-Blog CNAME and IP Address

Because our Subject Alternate Name SSL certificates can only accommodate 30 subjects, we needed a way to provision a blog onto a specific IP/CNAME/SSL certificate setup. This feature lets super admins visit a blog’s domain mapping settings and specify a CNAME or IP address different than the Network configuration. By default the blog domain mapping instructions will display the Network configuration.

Admin Only Primary Domain

Allow only super admins to change a blog’s primary domain. This is for when you want users to be able to setup aliases, but still have users redirected to the network blog address. Super admins will still be able to change the primary domain of a blog.

Don’t Force SSL for Admin URL Mapping

When remapping an admin URL, handle “/wp-admin/admin-ajax.php” a little differently. Usually the “force_ssl_admin” rule takes effect, but it shouldn’t for “admin-ajax.php” because it can be called from non-admin pages. If you force SSL on this callback, the same-origin policy might be violated for AJAX callbacks.

Preserve “redirect_to” After Login

After the Shibboleth login process, preserve the “redirect_to” URL parameter to handle redirection instead of just finishing at “wp-login.php”.

Additional URL Mappings

Also map “wp-login.php” for login and logout hooks. This lets SSL work properly in cases where you are not using SAN for your mapped domains.

Hook “allowed_redirect_hosts”

Let WordPress consider a redirect to a mapped domain as safe/allowed. This is done by hooking into the “allowed_redirect_hosts” filter.

 

Shibboleth and FERPA Suppression

Note: this document is still being revised as I explore the issue.

Many of the applications I support use Shibboleth not only for authentication, but also to populate and synchronize metadata about the user: display name, UIN, group memberships, or anything else available in our central registry. An upcoming change in how FERPA suppressed users are presented got me looking at how I could better use the Shibboleth Native Service Provider (SP) to handle metadata (I won’t be considering other SP’s).

Some of the attributes I request are only for convenience and are not mandatory for the application. When a user requests FERPA suppression the application should only have access to the smallest set of attributes required. One way to do this would be to modify each application to behave differently under suppression. But you can also use the Shibboleth SP itself to filter attributes, and leave the application alone.

At the University of Illinois, FERPA suppression is indicated in the iTrustSuppress attribute. If your SP isn’t already requesting this attribute, log in to the I-Trust Federation Registry and add it. We also have to configure the SP to map this attribute to a name in attribute-map.xml

<Attribute name="urn:oid:1.3.6.1.4.1.11483.101.3"id="iTrustSuppress"/>

You can check that the new attribute is being delivered by visiting “https://your-server.illinois.edu/Shibboleth.sso/Login?target=https://your-server.illinois.edu/Shibboleth.sso/Session”. If you’d like to see the attribute values, edit your shibboleth2.xml and set the “Session” handler “showAttributeValues” to “true”.

The last step is to edit attribute-policy.xml and define an iTrustSuppress policy. What attributes you allow or deny will depend on your applications, but here is an example of minimal attributes for WordPress (eppn and mail).

<afp:AttributeFilterPolicy id="iTrustSuppressPolicy">
    <!-- Only run this policy if the user is suppressed -->
    <afp:PolicyRequirementRule xsi:type="AttributeValueString"
            attributeID="iTrustSuppress"
            value="y"
            ignoreCase="true"/>

    <!--
        Rules for attributes we should always allow the application to see.
        This will vary based on the requirements of the application.

        * eppn: used for REMOTE_USER
        * mail: required by WordPress for all users
        * persistent-id: unused, but not identifiable information
    -->
    <afp:AttributeRule attributeID="eppn">
        <afp:PermitValueRule xsi:type="ANY"/>
    </afp:AttributeRule>
    <afp:AttributeRule attributeID="mail">
        <afp:PermitValueRule xsi:type="ANY"/>
    </afp:AttributeRule>
    <afp:AttributeRule attributeID="persistent-id">
        <afp:PermitValueRule xsi:type="ANY"/>
    </afp:AttributeRule>


    <!--
        Attributes allowed by another filter policy,
        but should be FERPA supressed. If they are explicitly allowed
        by any non-wildcard filter policy then the wildcard rule
        will not run.
    -->
    <afp:AttributeRule attributeID="affiliation">
        <afp:DenyValueRule xsi:type="ANY"/>
    </afp:AttributeRule>
    <afp:AttributeRule attributeID="unscoped-affiliation">
        <afp:DenyValueRule xsi:type="ANY"/>
    </afp:AttributeRule>
    <afp:AttributeRule attributeID="primary-affiliation">
        <afp:DenyValueRule xsi:type="ANY"/>
    </afp:AttributeRule>

    <!--
        Default rule is to suppress the value. This wildcard
        rule will not run if the attribute matches any non-wildcard
        rules in other policies.
    -->
    <afp:AttributeRule attributeID="*">
        <afp:DenyValueRule xsi:type="ANY"/>
    </afp:AttributeRule>
</afp:AttributeFilterPolicy>Test

If you have other policies defined then make sure that any attributes they explicitly allow should be allowed for a suppressed user, or are explicitly denied in the iTrustSuppressPolicy. Filter policies are evaluated in the order they are defined, so it would probably be best to add the iTrustSuppressPolicy at the end of the filter policy group.

Oracle Trigger to Manage Host IP

Disclaimer: I’m not an Oracle DBA, I only play one for work.

Preparing our systems for an Oracle Data Guard configuration, I had to decide how we’d manage the IP resource. How does the primary instance assign the IP address to the server? How does the secondary remove it?

I settled on using the database “AFTER STARTUP”, “BEFORE SHUTDOWN”, and “AFTER DB_ROLE_CHANGE” triggers. Here’s the sqlplus script I settled on:

BEGIN
	-- Create the database service clients will use to contact the system
	DBMS_SERVICE.CREATE_SERVICE(
		service_name=>'&&SERVICE_NAME',
		network_name=>'&SERVICE_NAME'
		);
EXCEPTION
	WHEN DBMS_SERVICE.SERVICE_EXISTS THEN DBMS_OUTPUT.PUT_LINE('Note: service &SERVICE_NAME exists.');
END;
/

DECLARE
	does_exist NUMBER;
BEGIN
	-- Remove any existing ICS_ORACLE_IP program and define a new one that calls our shell script.
	-- Takes one parameter, up or dn.
	SELECT COUNT(*) INTO does_exist FROM DBA_SCHEDULER_PROGRAMS WHERE PROGRAM_NAME = 'ICS_ORACLE_IP';
	IF does_exist = 1 THEN
		DBMS_SCHEDULER.DROP_PROGRAM('ICS_ORACLE_IP');
	END IF;

	DBMS_SCHEDULER.CREATE_PROGRAM(
		program_name=>'ICS_ORACLE_IP',
		program_type=>'EXECUTABLE',
		program_action=>'/usr/local/bin/oracle-ip.sh',
		number_of_arguments=>1,
		comments=>'Manage the oracle IP resource'
		);
	DBMS_SCHEDULER.DEFINE_PROGRAM_ARGUMENT(
		program_name=>'ICS_ORACLE_IP',
		argument_position=>1,
		argument_type=>'VARCHAR2'
		);
	DBMS_SCHEDULER.ENABLE('ICS_ORACLE_IP');

	-- Remove any ICS_ORACLE_IPUP job and define a new one. Passes one parameter 'up' to 
	-- the ICS_ORACLE_IP program.
	SELECT COUNT(*) INTO does_exist FROM DBA_SCHEDULER_JOBS WHERE JOB_NAME = 'ICS_ORACLE_IPUP';
	IF does_exist = 1 THEN
		DBMS_SCHEDULER.DROP_JOB('ICS_ORACLE_IPUP');
	END IF;

	DBMS_SCHEDULER.CREATE_JOB(
		job_name=>'ICS_ORACLE_IPUP',
		program_name=>'ICS_ORACLE_IP',
		auto_drop=>FALSE,
		comments=>'Bring the oracle IP resource up'
		);
	DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(
		job_name=>'ICS_ORACLE_IPUP',
		argument_position=>1,
		argument_value=>'up'
		);

	-- Remove any ICS_ORACLE_IPDN job and define a new one. Passes one parameter 'dn' to 
	-- the ICS_ORACLE_IP program.
	SELECT COUNT(*) INTO does_exist FROM DBA_SCHEDULER_JOBS WHERE JOB_NAME = 'ICS_ORACLE_IPDN';
	IF does_exist = 1 THEN
		DBMS_SCHEDULER.DROP_JOB('ICS_ORACLE_IPDN');
	END IF;

	DBMS_SCHEDULER.CREATE_JOB(
		job_name=>'ICS_ORACLE_IPDN',
		program_name=>'ICS_ORACLE_IP',
		auto_drop=>FALSE,
		comments=>'Bring the oracle IP resource down'
		);
	DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(
		job_name=>'ICS_ORACLE_IPDN',
		argument_position=>1,
		argument_value=>'dn'
		);

END;
/

-- Procedure that checks the database state and enables/disables the service and IP
-- address. Logic:
--
-- 1. If we are the primary and not shutting down, start the service and IP
-- 2. Else (standby, shutdown), stop the service and IP
CREATE OR REPLACE PROCEDURE ICS_SERVICE_SELECT (shutdown IN BOOLEAN := FALSE) AS
	role VARCHAR(30);
BEGIN
	SELECT DATABASE_ROLE INTO role FROM V$DATABASE;
	IF role = 'PRIMARY' AND NOT shutdown THEN
		BEGIN
			DBMS_SERVICE.START_SERVICE('&SERVICE_NAME');
		EXCEPTION
			WHEN DBMS_SERVICE.SERVICE_IN_USE THEN
				NULL;
		END;

		DBMS_SCHEDULER.RUN_JOB('ICS_ORACLE_IPUP');
	ELSE
		BEGIN
			DBMS_SERVICE.STOP_SERVICE('&SERVICE_NAME');
		EXCEPTION
			WHEN DBMS_SERVICE.SERVICE_NOT_RUNNING THEN
				NULL;
		END;

		DBMS_SCHEDULER.RUN_JOB('ICS_ORACLE_IPDN');
	END IF;
END ICS_SERVICE_SELECT;
/

CREATE OR REPLACE TRIGGER ICS_SERVER_STARTUP AFTER STARTUP ON DATABASE
BEGIN
	ICS_SERVICE_SELECT;
END;
/

CREATE OR REPLACE TRIGGER ICS_SERVER_SHUTDOWN BEFORE SHUTDOWN ON DATABASE
BEGIN
	ICS_SERVICE_SELECT(shutdown=>TRUE);
END;
/

CREATE OR REPLACE TRIGGER ICS_SERVER_ROLE_CHANGE AFTER DB_ROLE_CHANGE ON DATABASE
BEGIN
	ICS_SERVICE_SELECT;
END;
/

Create a script at “/usr/local/bin/oracle-ip.sh” that takes one parameter (“up” or “dn”) and performs the proper IP address management. Remember that it needs to run as the “oracle” user, so use sudo where appropriate.