Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • Making HTTPS Everywhere

    Making HTTPS Everywhere

    With the advent of Let’s Encrypt, introducing free and easy SSL certificates for everyone, and the fact that Plesk, cPanel, and home grown Panels like DreamHost’s all providing easy ways to install certs, renew them, and support them, we’re finally inching our way to the dream of HTTPS Everywhere.

    Why HTTPS?

    The S makes it secure, and the green lock on a browser tells a person that their visit is safer, encrypted, and obscures sensitive data. It means a visit is confidential. It means the site is the real site. It cannot be easily monitored, modified, or impersonated.

    While this blog has no sensitive data of yours, it does accept (require) your email when you leave a comment. You don’t want everyone knowing that, I suspect. You probably don’t want everyone grabbing your IP.

    Why do we care about security in general? Because nothing is non-sensitive anymore. Everything we do and say on the Internet can be used against us. Entering in your mother’s maiden name on a form over HTTP? Someone can snipe that and use it to steal your identity. Use the same password on multiple accounts, one of which is HTTP? Your code can be stolen. The list goes on and one.

    How I Turned This Site HTTPS Everywhere

    Every single domain on ipstenu.org is now https. Everyone either has the Let’s Encrypt certificate or a Comodo one. First I turned on Let’s Encrypt. Then I used WP-CLI to search and replace my urls:

    $ wp search-replace http://halfelf https://halfelf
    $ wp search-replace http://ipstenu https://ipstenu
    

    And so on and so forth down the line.

    Next I checked my mu-plugins folder and my content folder to make sure none of my home grown code was hardcoding in http (it wasn’t), and updated my wp-config.php to include this:

    define('FORCE_SSL_ADMIN', true);
    define('FORCE_SSL_LOGIN', true);
    

    That probably wasn’t required but why not? Finally I tossed this into my .htaccess:

    # Force non WWW and SSL for everyone.
    <IfModule mod_rewrite.c>
    	RewriteEngine On
    
    	RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
    	RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    
    	RewriteCond %{HTTPS} off
    	RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    <IfModule mod_rewrite.c>
    

    Really. That’s all it took to swap it all to https everywhere here.

    Gotchas

    Not all my plugins were happy about this.

    Most were, actually, which was nice, but a couple did some incredibly stupid things with hardcoded http resources. Fixing them for myself is trivial. For others… I recommend WordPress HTTPS or Really Simple SSL, both of which will let you force https for all URLs or block the http ones.

    For the most part, with WordPress, you don’t need to worry about this. In recent years, the ability to force SSL from within WP itself has gotten better and better. The problem has always been our themes and plugins.

    Other than that, it’s been pretty smooth going.

    Non WordPress

    But… what about my non-WordPress sites? Yeah, you know I have them.

    Well my ZenPhoto20 site doesn’t run any extensions, so I just checked the box for using SSL and went on my way. I’d cleverly written all my themeing with protocol-less URLs (\\example.com\my\path\file.css). While that’s really an anti-pattern, and https should be used whenever possible, I had everything in one file so I search/replace’d that and it was done.

    My Hugo site required two changes to a config file. It looked like this:

      StaticURL = "https://example-static.net"
      HomeURL = "https://example.net"
    

    The reason I did that was so my templates could look like this:

    <link rel="shortcut icon" href="{{ .Site.Params.StaticURL }}/images/favicon.ico">
    

    Once I saved the variables in my config, I could push the site (which automatically rebuilds and deploys) and be done.

  • Dog Shaming Disclosure

    Dog Shaming Disclosure

    You’ve probably seen this. A dog with something around their neck saying “I ate the carpet.”

    We think that it’s funny because the dog often has no real idea what they did wrong, and we’re embarking the absolute absurdity of the moment. At the same time, we’re terrible people because we’re mocking a creature who can’t understand what we’re doing. So we’re pretty shitty people.

    This is not about any one specific company or group or person. This happens weekly. I see people tweet and post and point fingers in public well before they ping people privately or directly. I see people come into Slack and announce “This is vulnerable!” I see people post in forums the same thing. Some of these people don’t know any better, but worst is when they do know better.

    While we bandy about the need for responsible disclosure of security issues, and the need for quick resolutions, I feel that we are often too quick to point and shame and accuse. We want to get the news out about a problem so fast, to get people’s eyes and attention, that we forget about the humanity behind the product.

    Also we forget how hard we hit.

    Public Embarrassment

    When someone screws up in public, they are shaming themselves. Like the Olympic diver who belly flopped, or the hurdler who ran right into the first hurdle, when gaffs are televised world wide. They go on YouTube, they’re tweeted and pointed at for years. We will remember them for a long time. But that is embarrassment someone has done to themselves. People are people and release press notices too soon, push code early, and make mistakes. When someone does it to themselves, it’s galling and embarrassing, and they feel terrible. Their friends tell them it happens to everyone, and to learn from the mistake and do better next time.

    Public Shaming

    On the other hand, there is the ‘friend’ who publicly shouts that someone screwed up. They are metaphorically hanging a sign around someone’s neck and saying they suck. To the world. Now yes, they screwed up, but a human’s natural reaction to that is anger, pain, and a lack of desire to fix it because they’ll just screw up again.

    To make this more simple, public shaming creates a bad environment. It discourages innovation with fear.

    Responsible Disclosure

    Two years ago, Andrew Nacin talked about how security is nuances.

    There will always be individuals who want everything to be fully disclosed, and there are some great arguments for that. I’m not trying to sway you one way or the other. But if you’re trying to do the right thing — you’re doing full disclosure in the interest of users, possibly even providing a patch or steps to mitigate — working with the vendor is a good way to ensure you haven’t missed anything.

    Unlike Nacin, I do want to sway you to one side. I want to sway you to the side of communication.

    If you find a security hole in a product, the first reaction should be to reproduce it as best you can, write up exactly how it can be exploited with examples and Proofs of Concepts, and then contact the developers/vendors about it. Give them some time to reply. Ask them what they would agree a reasonable disclosure timeframe could be, talk and negotiate what would make sense for the product, the situation, and the developers. I want you to think about when releasing the information will harm the fewest people.

    Being responsible means thinking beyond the simple “This should be fixed for people.” It means “This should not put more people in danger.” It means you have to look at the big picture. Is it reasonable to expect people to update right away and, thus, you can release full disclosure with the update, or is it more realistic that it may take a while? What about bundled products? Will they get the alerts timely or not?

    Forget First, Embrace Most

    But above all else, we have got to stop this behavior of ‘First!’ Because that’s what’s going on. People are in a rush to be the first to report a problem or an issue, and in doing so they forget who they’re doing this for. Forget being first. Start caring about the people you’re posting the information for. Is this helping the most people?

    Publicly dragging someone through the muck, starting a witch hunt, just because they screwed up doesn’t help anything. It makes for an unhealthy developer community, and it makes for user base that cannot trust the developers.

  • Let’s Encrypt cPanel

    Let’s Encrypt cPanel

    On August 10th, cPanel announced provisional support for Let’s Encrypt via AutoSSL.

    For hosts like DreamHost to be able to implement Let’s Encrypt is a lot easier than a behemoth like cPanel. See, DreamHost only had to make sure it worked on their own servers. They have a homegrown panel that they have 100% full control over. Adding in how to install the code on all servers and how to integrate it requires less testing than cPanel, who has to make sure everyone who uses cPanel can use this.

    As of version 58 of cPanel, everyone can. And it works.

    Installation

    Log in via SSH and run this as root:

    $ /scripts/install_lets_encrypt_autossl_provider
    

    That installs everything you need. Keep in mind, this only adds LE to the AutoSSL feature. It’s AutoSSL that whips up SSL certificates for cPanel accounts. Doing this install does not install certs on your domains. We have to configure it for that.

    Configuration

    Once you’ve installed the code, go to WHM: Home » SSL/TLS » Manage AutoSSL and set it to Let’s Encrypt:

    The Auto-SSL page

    If it’s your first time, yes, check Create a new registration with the provider. because you’re new. You only need to mess with that if you’re new or have to reset registration for some reason.

    By default, AutoSSL is set to run based on your “Feature List Setting” (under Home » Packages » Feature Manager » Edit Feature List). Mine has it checked, which means it will automatically run.

    Adding Existing Domains

    This worked great except I had a bunch of domains using StartSSL. First off, I adore StartSSL, and the recent changes to their website make it so much easier to use. But I was using it for external free certificates where I didn’t want to pay for them, on domains that never see money. Some of them (most of them) I wanted to convert to LE.

    For that, I deleted the StartSSL certificates in WHM and cPanel for the domains/account in question. Then I went to AutoSSL, clicked on the tab “Manage Users,” and clicked “Check USERNAME.” I did not pick check all users (which is at the top of the page) because I don’t want to check all users.

    Adding New Domains

    I love this part.

    Do nothing.

    No, really. Add the new domain, wait twelve hours, boom. New certificate. If you have to have it right now, go into WHM and click check for that user. But it’s automatic. Hence ‘Auto’ SSL you see.

    Caveats!

    This is something only controlled by the server admin. Per-site cPanel doesn’t get an option, however if they delete the LE cert and add their own, that will override it.

    There’s a limit to how many times you can make certificates and how many you can make. As the warning says:

    Certificates that Let’s Encrypt provides through AutoSSL can secure a maximum of 100 domains per virtual host.
    Let’s Encrypt will issue a maximum of 20 certificates per week that contain a domain or its subdomains. If you include subdomains of a domain on more than 20 certificates, Let’s Encrypt will issue those during the next window, up to the limit for that week.

    If you’re using a wildcard subdomain (*.ipstenu.org for example) in order to make things easier with Multisite, this won’t work. You’ll see a ton of errors in your logs. Not to mention it won’t make SSL certs for all the virtual subdomains. That’s because they’re too virtual. You’ll have to make an actual add-on subdomain or use a domain alias for LE to pick that up.

    You can’t revoke a certificate either, which can be a problem should there be a security issue along the lines of Heartbleed. When that happened, we all had to reset our SSL certs as well as patch our servers. Lots of fun. Should that happen again, cPanel users will have a big problem.

    It’s because of that I don’t want to use Let’s Encrypt on everything. I’ll use it on this domain, and my other normal ones, but my WMH domain and my stores use a Comodo Certificate.

  • Elasicsearch as a Service

    Elasicsearch as a Service

    Search is hard. Searching when you have custom meta data in post is harder. By default, WordPress does not search your custom meta data. And my LezWatchTV site is 75% custom meta data.

    I’d been using Google Search, but that has a lot of issues of it’s own like privacy, ads, accuracy, and most importantly, no way to tune it. I decided to try out ElasticSearch since I knew that was what WordPress.org’s internal search engine was going towards. After I added custom post meta to my search content, this post was going to be about how to install Elasticsearch on an ELK stack on DreamCompute, which turned out to be rather easy if time consuming and messy. And getting WordPress to work with it was as easy as installing the ElasticPress plugin (thank you 10up).

    What was complicated was making Elasticsearch work remotely. By default, it wants to only be accessible locally for your own security. But adding in Shield and still having all the logs and pretty things to understand what was happening and how to manage it when it was all new escalated quickly. It was simply too much all at once for me. Instead I decided to look into Elasticsearch as a service.

    There are a lot of options here,

    Self Managed

    I know I said ‘as a service’ but you really can use DigitalOcean or DreamCompute to do this. And there’s all sorts of documentation about how to do it available (like DigitalOcean’s ‘How to install the ELK Stack on Ubuntu 14.04’ which works on DreamCompute too). And Amazon Elasticsearch is also an option here.

    But… they’re all very self-managed. They require you to jump into servers, run a lot of commands, and they’re not new user friendly. Look, I get that this is complicated stuff, but people aren’t going to know if they want to learn all this if you make it monumental to get into.

    Services

    You can break these down into two main types.

    Enterprise Level:

    Free ‘Trials’:

    I wanted to use something ‘free’ to get started so I could figure out what I wanted to do and how to properly use Elasticsearch before deciding if I wanted to pay. But also I wanted to figure out exactly to do with search. Therefore I needed something ‘free’ to test with, something with logs, that would help me understand it all. I ended up trying both Bonsai and Searchly. While Bonsai gave me more room, Searchly had more information to the interface, but neither had a ‘Hey, here’s how you tune Elasticsearch!’ page.

    Neither had Kibana 4 though, which is a little sad.

    So when you don’t know how to do ‘anything’ with Elasticsearch, what can you test? The same search. I checked which was faster, which was more accurate, and which had the results I wanted. Bonsai was the winner here, so that’s what I went with.

    Integrating WordPress

    Thankfully this is the easy part.

    Install the ElasticPress plugin. Go to Settings -> ElasticPress and add in the URL from your Bonsai panel as your Host. It should look like https://username:password@yourcluster.us-west-2.bonsai.io (with some variation based on location). Save, press the ‘Run Index’ button, and you’re done.

    The nice thing about the plugin is if it breaks (like the service goes down), the plugin reverts to WordPress search! Which isn’t great, but … well.

    Next? How do I tune Elasticsearch?!

  • Search Options for Custom Post Data

    Search Options for Custom Post Data

    I use CMB2 to add in a bunch of custom meta data for my posts on a site. Seeing as I’m using it to allow layouts and formats to be consistent, it’s not surprising that I’ve chosen to split out my data like that. In another world, maybe it would be done differently, but this works.

    Except that search sucks. WordPress doesn’t search custom post meta out of the box which just kills me. That meant all the data I stored in for names and dates was never getting searched. There are two ‘easy’ solutions for this at least.

    Google Search

    Ew. I know. But ew. Since I’m using Genesis as my theme, it’s not super hard, just a little weird. Assuming you already have a Custom Search Engine set up, and you’re using Genesis, here’s what to do next.

    First I added this into my functions-site.php (note: I made a functions-site.php file so I can easily update my functions.php file on the rare occasion I need to update the child theme – it’s really rare – but also so I always know what’s me and what was Genesis):

    /* Google Custom Search Engine */
    add_filter( 'genesis_search_form', 'helf_search_form', 10, 4);
    function helf_search_form( $form, $search_text, $button_text, $label ) {
        $onfocus = " onfocus=\"if (this.value == '$search_text') {this.value = '';}\"";
        $onblur = " onblur=\"if (this.value == '') {this.value = '$search_text';}\"";
        $form = '<form method="get" class="searchform search-form" action="' . home_url() . '/search" >' . $label . '
    <input type="text" value="' . esc_attr( $search_text ) . '" name="q" class="s search-input"' . $onfocus . $onblur . ' /><input type="submit" class="searchsubmit search-submit" value="' . esc_attr( $button_text ) . '" /></form>';
        return $form;
    }
    

    Then I made a custom page template thanks to Rick Duncan:

    <?php
    /*
     * Template Name: Google Custom Search Engine
     *
     * This file adds the Google SERP template to our Genesis Child Theme.
     *
     * @author     Rick R. Duncan
     * @link       http://www.rickrduncan.com
     * @license    http://www.opensource.org/licenses/gpl-license.php GPL v2.0 (or later)
     *
     */
    
    //* Force Full-Width Layout
    add_filter( 'genesis_pre_get_option_site_layout', '__genesis_return_full_width_content' );
    
    //* Add Noindex tag to the page
    add_action( 'genesis_meta', 'lez_noindex_page' );
    function lez_noindex_page() {
    	echo '<meta name="robots" content="noindex, follow">';
    }
    
    //* Insert Google CSE code into <head> section of webpage
    add_action( 'genesis_meta', 'lez_google_cse_meta', 15 );
    function lez_google_cse_meta() { ?>
    
    	<script>
    	  (function() {
    	    var cx = '017016624276440630536:tpoclrwnxyy';
    	    var gcse = document.createElement('script');
    	    gcse.type = 'text/javascript';
    	    gcse.async = true;
    	    gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
    	    var s = document.getElementsByTagName('script')[0];
    	    s.parentNode.insertBefore(gcse, s);
    	  })();
    	</script><?php
    }
    //* Add custom body class
    add_filter( 'body_class', 'lez_add_body_class' );
    function lez_add_body_class( $classes ) {
    
       $classes[] = 'google-cse';
       return $classes;
    
    }
    //* Remove standard Genesis loop and insert our custom page content
    remove_action( 'genesis_loop', 'genesis_do_loop' );
    add_action( 'genesis_loop', 'lez_custom_content' );
    function lez_custom_content() { ?>
    
    	<div itemtype="http://schema.org/SearchResultsPage" itemscope="itemscope">
    		<header class="entry-header">
    			<h1 itemprop="headline">
        			<?php echo get_the_title($ID); ?>
        		</h1>
        	</header>
        	<div class="entry-content" itemprop="text">
        		<?php echo get_the_content();
        		//* Obtain querystring value if present and display on-screen
    			if ((isset($_REQUEST['q'])) && (!empty($_REQUEST['q']))) {
        			$query= $_REQUEST['q'];
        			echo '<strong>You Searched For:</strong> <em>'.$query.'</em>';
    			}
    			else {
    				echo 'Please enter a search phrase.';
    			}
    			if ( is_active_sidebar( 'google-cse' ) ) {
    				dynamic_sidebar( 'google-cse' );
    			}
    			?>
        		<gcse:searchresults-only></gcse:searchresults-only>
        	</div>
        </div>
    
    <?php }
    genesis();
    

    Finally I added a page called “Search Results” and assigned it this template. Done. Google, who searches the whole page content, will get everything. It just looks like Google.

    Having WordPress search your Custom Post Meta

    This was surprisingly annoying, but not as hard as all that. Adam Balee wrote Search WordPress by Custom Fields without a Plugin which, I know, is ‘without a plugin’ and sort of silly, but I put that in as an MU plugin and it worked perfectly!

    <?php
    /**
     * Extend WordPress search to include custom fields
     *
     * http://adambalee.com
     */
    
    /**
     * Join posts and postmeta tables
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join
     */
    function cf_search_join( $join ) {
        global $wpdb;
    
        if ( is_search() ) {    
            $join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
        }
        
        return $join;
    }
    add_filter('posts_join', 'cf_search_join' );
    
    /**
     * Modify the search query with posts_where
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where
     */
    function cf_search_where( $where ) {
        global $pagenow, $wpdb;
       
        if ( is_search() ) {
            $where = preg_replace(
                "/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
                "(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where );
        }
    
        return $where;
    }
    add_filter( 'posts_where', 'cf_search_where' );
    
    /**
     * Prevent duplicates
     *
     * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_distinct
     */
    function cf_search_distinct( $where ) {
        global $wpdb;
    
        if ( is_search() ) {
            return "DISTINCT";
        }
    
        return $where;
    }
    add_filter( 'posts_distinct', 'cf_search_distinct' );
    

    This is not the most efficient search, I know. But it works and gets my data where it’s needed.

  • Pagination and Static Front Pages

    Pagination and Static Front Pages

    This is a post skewed towards the Genesis Framework. Actually, if you’re not using the Genesis Metro Pro theme, I don’t know how well this will work.

    My problem was simple. I used the Metro Pro Static Front Page to show some widgets and then custom displays of posts via those widgets. It works pretty darn well and looks like this:

    Metro Pro's Static Front Page

    There was just one small issue. It doesn’t show me pagination at the bottom of the page. Oh and the normal method of example.com/page/2/ just showed me the same front page over and over. Not what I wanted.

    One way I could work around this would be to treat the front page as a static front page and make a “blog” page. Except then my urls would be example.com/blog/page/2 and I’d have duplicate content on example.com/blog/ which is not desirable. Causing me more frustration was the fact that the documentation said this:

    If no widgets are placed in any of the home page specific widget areas, a blog-style home page will be displayed.

    What I wanted was that blog-style page on the sub pages, along with navigation.

    Show Navigation Links

    This part was easy. In the file front-page.php I edited the function function metro_homepage_widgets() to have this at the bottom:

    genesis_posts_nav();

    Really, that was it. Now I had navigation! But (as I already knew) the navigation didn’t work properly.

    Fix Paged Content

    At the top of the front-page.php file is a call to add an action with all the metro_home_genesis_meta content. I wrapped that in a check to see if the page we’re on is a ‘paged’ page using is_paged(), which specifically checks if the query is for paged result and not for the first page.

    if ( !is_paged() ) {
    	add_action( 'genesis_meta', 'metro_home_genesis_meta' );
    }
    

    Again, really, that was it.