<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[drew jess]]></title><description><![CDATA[the tech blog of a vaguely cheerful, cloudy, linuxy fellow.]]></description><link>https://blog.hybby.net/</link><image><url>https://blog.hybby.net/favicon.png</url><title>drew jess</title><link>https://blog.hybby.net/</link></image><generator>Ghost 5.27</generator><lastBuildDate>Wed, 23 Jul 2025 13:04:54 GMT</lastBuildDate><atom:link href="https://blog.hybby.net/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Monitoring mTLS Protected APIs Without a Client Certificate]]></title><description><![CDATA[<p>We run a bunch of APIs secured by mTLS at my current place. Third parties establish connections to our API webservers by presenting a client certificate. We check that we consider it valid and if so, permit access to the relevant resources.</p><p>As our part of the mTLS handshake, we</p>]]></description><link>https://blog.hybby.net/monitoring-mtls-protected-apis-without-a-client-certificate/</link><guid isPermaLink="false">66705881525b4900018fbb64</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Mon, 17 Jun 2024 16:30:41 GMT</pubDate><content:encoded><![CDATA[<p>We run a bunch of APIs secured by mTLS at my current place. Third parties establish connections to our API webservers by presenting a client certificate. We check that we consider it valid and if so, permit access to the relevant resources.</p><p>As our part of the mTLS handshake, we present our server certificate when clients connect. We&apos;ve got a vested interest in knowing how long is left on that certificate&apos;s validity, so that we can plan to renew it in plenty of time.</p><p>We&apos;re big users of DataDog, and for most TLS monitoring, we&apos;ve found the <a href="https://docs.datadoghq.com/integrations/tls/"><em>tls</em> check</a> that ships with the DataDog Agent to be superb. It connects to an endpoint, calculates <code>tls.days_left</code> and we can use that to configure up a monitor that alerts us in plenty of time. It even supports mTLS, with options provided for <code>tls_cert</code> and <code>tls_private_key</code> to allow our agent to present a client cert. Great, right?</p><p>Couple of problems here though:</p><ul><li>Issuing yourself a client cert to perform &quot;monitoring&quot; is one more set of credentials that might have access to your app - effectively a &quot;back door&quot;. Sure, you could write some logic into your app or webserver config to treat certain certs differently. But it&apos;s a bit clunky and easy to mess up.</li><li>Monitoring expiry of a server cert with a client cert? Now you have two things to monitor. How long is your monitoring cert valid for? Don&apos;t say forever! You need to also make sure you&apos;re monitoring and renewing that client cert appropriately alongside your server cert. Failure to do so means your monitoring solution is <em>kaput</em>. Twice the work.</li></ul><p>Using the DataDog <em>tls </em>check without <code>tls_cert</code> and <code>tls_private_key</code> to monitor an mTLS API isn&apos;t an option, either. Try it and you&apos;ll see the following:</p><pre><code>$ datadog-agent check tls
...
{
  &quot;check&quot;: &quot;tls.cert_validation&quot;,
  ...
  &quot;status&quot;: 2,
  &quot;message&quot;: &quot;[SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:1129)&quot;,
  ...
}</code></pre><p>And it makes sense on the face of it. DataDog&apos;s not keen on reporting the success of the TLS connection until a full handshake&apos;s been made. But for mTLS APIs, we <em>need</em> that client cert to complete the handshake. So the <em>tls</em> check is no good here.</p><p>Where do we go from here? Well, the <code>openssl</code> CLI tool seems to be able to fetch a server certificate just fine when you point it at the same API:</p><figure class="kg-card kg-code-card"><pre><code>$ openssl s_client -showcerts -servername foo.com \
  -connect foo.com:443 2&gt;/dev/null &lt;/dev/null | \
    openssl x509 -noout -dates
  notBefore=Apr 26 10:56:57 2024 GMT
  notAfter=Apr 26 10:56:57 2025 GMT</code></pre><figcaption>Fetching a server cert from an mTLS enabled server without a client cert (<a href="https://stackoverflow.com/questions/7885785/using-openssl-to-get-the-certificate-from-a-server">source</a>)</figcaption></figure><p>Why can <code>openssl</code> do this where DataDog can&apos;t? </p><p>I think it&apos;s due to the ordering of the <a href="https://www.cloudflare.com/en-gb/learning/access-management/what-is-mutual-tls/">handshake operation</a>. Namely these steps:</p><ol><li>Client connects to server</li><li>Server presents its TLS certificate <em>(ding, ding, ding!)</em></li><li>Client verifies the server&apos;s certificate</li><li>Client presents its TLS certificate <em>(here&apos;s where we fall down...)</em></li><li>...etc...</li><li>Handshake established!</li></ol><p>All the info we need is in step 2. We don&apos;t need to get to step 6. The <code>openssl</code> tool clearly knows that and doesn&apos;t press the issue. So how do we get Datadog to do the same?</p><p>Well, we can write a DataDog <a href="https://docs.datadoghq.com/developers/custom_checks/">custom check</a> to submit our own metric(s). This framework makes it super-easy to write some Python to run, calculate some metric and submit the value up to DataDog for easy use. What code to write, though?</p><p>Luckily, the embedded DataDog Agent&apos;s Python comes with <code>pyOpenSSL</code> available to use. This is a Python library that&apos;s a wrapper around <code>openssl</code>, and operates differently enough to the standard Python <code>ssl</code> library (which DataDog and its <em>tls</em> check uses) to be useful to us when writing a custom check. </p><p>Namely, we can attempt to make a connection to an mTLS enabled server, catch the <code>sslv3 alert bad certificate</code> exception that will undoubtedly result, then use the information we&apos;ve gathered already without worrying about the overall status of the handshake - we weren&apos;t going to meaningfully send any data to the server, anyway. We&apos;ve already been sent the server cert and only care about its validity - so let&apos;s use it. </p><p>We have everything we need to decide whether we trust the presented cert already, too - our local CA truststore. We don&apos;t need a full handshake for trust purposes.</p><p>A straight-up Python script to do this might look something like:</p><figure class="kg-card kg-code-card"><pre><code class="language-Python">#!/usr/bin/python3
import OpenSSL
import socket
import datetime

host = &quot;foo.com&quot;
port = 443

sock = sock.create_connection((host, port))
context = OpenSSL.SSL.Context(OpenSSL.SSL.TLS_METHOD)
connection = OpenSSL.SSL.Connection(context, sock)
connection.set_connect_state()

# think of the below as the `-servername` arg to `openssl`
# if you need to adjust it, feel free to!
connection.set_tlsext_host_name(host.encode(&apos;utf-8&apos;))

# catch the expected &apos;sslv3 alert bad certificate&apos; error on mTLS APIs
# and continue anyways, knowing we&apos;ve been sent the server cert already anyway
try:
  connection.do_handshake()
  cert = connection.get_peer_certificate()
except:
  cert = connection.get_peer_certificate()
  
# calculate expiry and output to screen
today = datetime.datetime.now()
exp = datetime.datetime.strptime(
    cert.get_notAfter().decode(&apos;ascii&apos;), 
    &apos;%Y%m%d%H%M%SZ&apos;
  )

left_secs = int(exp - today).total_seconds())
left_days = left_secs / 60 / 60 / 24

print(cert.get_subject().commonName)
print(left_secs)
print(left_days)</code></pre><figcaption>No handshake? No problem! Let&apos;s proceed regardless (<a href="https://stackoverflow.com/questions/61737856/get-a-server-certificate-despite-handshake-failure-in-python">source</a>)</figcaption></figure><p>Translate this into a DataDog check using their docs, and ship it up as a metric with <code>self.gauge()</code> and you&apos;ve got your validity metrics for an mTLS API&apos;s server cert without having to use a client cert. Use metrics these in your cert monitors.</p><p>It&apos;ll work with non-mTLS APIs too (where the handshake completes), but in those cases I&apos;d probably recommend just using the <em>tls</em> check - it&apos;s likely more bulletproof.</p><p>Hope this helps make someone else&apos;s life a little easier.</p>]]></content:encoded></item><item><title><![CDATA[Launching Encrypted EBS Volumes via CloudFormation]]></title><description><![CDATA[<p>Today, I was launching a CloudFormation template which contained an EBS volume which I wanted to be encrypted with an <strong>AWS Key Management Service (KMS) Customer Managed Key (CMK)</strong>.</p><p>The resource in the template looked like this:</p><!--kg-card-begin: markdown--><pre><code class="language-json">&quot;EncryptedVolume&quot;: {
  &quot;Type&quot;: &quot;AWS::EC2::Volume&quot;,
  &quot;DeletionPolicy&</code></pre>]]></description><link>https://blog.hybby.net/launching-encrypted-ebs-volumes-via-cloudformation/</link><guid isPermaLink="false">5fce44d80b18d10001f01ad2</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Mon, 07 Dec 2020 15:43:57 GMT</pubDate><content:encoded><![CDATA[<p>Today, I was launching a CloudFormation template which contained an EBS volume which I wanted to be encrypted with an <strong>AWS Key Management Service (KMS) Customer Managed Key (CMK)</strong>.</p><p>The resource in the template looked like this:</p><!--kg-card-begin: markdown--><pre><code class="language-json">&quot;EncryptedVolume&quot;: {
  &quot;Type&quot;: &quot;AWS::EC2::Volume&quot;,
  &quot;DeletionPolicy&quot;: &quot;Snapshot&quot;,
  &quot;Properties&quot;: {
    &quot;AvailabilityZone&quot;: &quot;eu-west-1a&quot;,
    &quot;Encrypted&quot;: true,
    &quot;KmsKeyId&quot;: &quot;arn:aws:kms:eu-west-1:123456780123:key/blah-blah&quot;,
    &quot;Size&quot;: 64,
    &quot;VolumeType&quot;: &quot;gp2&quot;
  }
}
</code></pre>
<!--kg-card-end: markdown--><p>I created the stack containing this resource and waited for the resource to create.</p><p>It seemed to take a while. And when CloudFormation resource creations take a long time, you can bet your bottom dollar that it&apos;s not a good thing. Half-an-hour later, my stack entered the <strong>ROLLBACK_COMPLETE </strong>state. My only hints:</p><!--kg-card-begin: markdown--><ul>
<li><strong>EncryptedVolume</strong>: Volume vol-12345678abcd1234 is still creating</li>
<li><strong>CREATE_FAILED</strong>: Resource creation cancelled</li>
<li>The following resource(s) failed to create: <strong>EncryptedVolume</strong>. Rollback requested by user.</li>
</ul>
<!--kg-card-end: markdown--><p>What happened? &#xA0;It wasn&apos;t clear. &#xA0;Checking <strong>CloudTrail</strong> showed that the <strong>ec2:CreateVolume</strong> call definitely fired from CloudFormation. &#xA0;I even had a volume ID returned. &#xA0;But checking the <strong>EC2 -&gt; Volumes</strong> console for that ID showed up nothing. It&apos;s as if it didn&apos;t exist.</p><p>I&apos;ve experienced in the past that AWS services sometimes act really weird if they&apos;re asked to use a KMS key that they don&apos;t have permission to. &#xA0;For example, if you ask an <strong>AutoScaling Group </strong>to launch volumes with boot volumes encrypted by an AWS KMS CMK, you&apos;re gonna have a bad time unless you <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/key-policy-requirements-EBS-encryption.html">configure your key policy properly</a>. But this at least is relatively well documented through troubleshooting documentation that you can find when searching for the <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ts-as-instancelaunchfailure.html#ts-as-instancelaunchfailure-12">instance launch failure</a>.</p><p>This time around, I didn&apos;t even have an error to work with. But while searching for something completely unrelated (volume deletion policies, actually), I happily and quite accidentally found some <a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.Volume.html#encryptionkey">relevant looking information</a> in the AWS Cloud Development Kit (CDK) EC2 Volume docs.</p><p>Turns out that for EC2 to create an EBS Volume encrypted with a CMK, the principal creating the volume has to have permission to call the following policy actions (note the conditions required to lock these permissions down using the principle of least privilege):</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
...
  &quot;Action&quot;: [
    &quot;kms:DescribeKey&quot;,
    &quot;kms:GenerateDataKeyWithoutPlainText&quot;,
  ],
  &quot;Condition&quot;: {
    &quot;StringEquals&quot;: {
      &quot;kms:ViaService&quot;: &quot;ec2.&lt;aws-region&gt;.amazonaws.com&quot;,
      &quot;kms:CallerAccount&quot;: &quot;&lt;aws-account-id&gt;&quot;
    }
  }
...
}
</code></pre>
<!--kg-card-end: markdown--><p>I definitely had the IAM permissions in place to do this, as I was running as a user with the <strong>AdministratorAccess</strong> policy attached. &#xA0;However, I remembered that in order to allow IAM to work on a KMS CMK, the CMK&apos;s <a href="https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html#key-policy-default-allow-root-enable-iam">key policy had to be configured to grant those actions to the root principal of the account that the key was created in</a>.</p><p>However, I&apos;d created my KMS CMK via the <a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-kms.Key.html">AWS Cloud Development Kit (CDK)</a> which should contain a sensible, out-of-the-box key policy to allow the key to be used by resources via IAM.</p><p>I checked the key policy of my KMS CMK that I had created. &#xA0;It turns out CDK had created the following key policy:</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: {
        &quot;AWS&quot;: &quot;arn:aws:iam::123456789012:root&quot;
    },
    &quot;Action&quot;: [
        &quot;kms:Create*&quot;,
        &quot;kms:Describe*&quot;,
        &quot;kms:Enable*&quot;,
        &quot;kms:List*&quot;,
        &quot;kms:Put*&quot;,
        &quot;kms:Update*&quot;,
        &quot;kms:Revoke*&quot;,
        &quot;kms:Disable*&quot;,
        &quot;kms:Get*&quot;,
        &quot;kms:Delete*&quot;,
        &quot;kms:ScheduleKeyDeletion&quot;,
        &quot;kms:CancelKeyDeletion&quot;,
        &quot;kms:GenerateDataKey&quot;,
        &quot;kms:TagResource&quot;,
        &quot;kms:UntagResource&quot;
    ],
    &quot;Resource&quot;: &quot;*&quot;
}
</code></pre>
<!--kg-card-end: markdown--><p>Sure enough, we were missing the <strong>GenerateDataKeyWithoutPlainText</strong> policy action in the key policy. &#xA0;I followed the documentation I had found to add the relevant policy action (with the correct conditions, as written earlier in this post). After doing that, recreating the stack yielded me with a freshly-minted, encrypted volume. Problem solved.</p><p>I&apos;m hoping that in the future, AWS can implement faster failing (without being stuck in <strong>CREATE_IN_PROGRESS</strong> for half-an-hour) and better error reporting (even a searchable string!) for this type of error. &#xA0;</p>]]></content:encoded></item><item><title><![CDATA[AWS AutoScalingPlans, RollingUpdates and CloudFormation]]></title><description><![CDATA[
We’ve been exploring the world of AWS AutoScaling Plans recently. Being captivated by the tasty-looking Predictive Scaling for EC2, we were…
]]></description><link>https://blog.hybby.net/aws-autoscalingplans--rollingupdates-and-cloudformation/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedae</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Mon, 18 May 2020 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>We&#x2019;ve been exploring the world of <a href="https://docs.aws.amazon.com/autoscaling/plans/userguide/auto-scaling-getting-started.html">AWS AutoScaling Plans</a> recently. Being captivated by the tasty-looking <a href="https://aws.amazon.com/blogs/aws/new-predictive-scaling-for-ec2-powered-by-machine-learning/">Predictive Scaling for EC2</a>, we were wooed into replacing all of our <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-policy.html">AWS::AutoScaling::ScalingPolicy</a> resources for equivalent, shiny new <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscalingplans-scalingplan.html">AWS::AutoScalingPlans::ScalingPlan</a> resources.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-LhVLJgp4dW9bthMikSq68w.png" class="kg-image" alt loading="lazy"><figcaption>&#x201C;There&#x2019;s scaling,&#x201D; says Jeff Barr, &#x201C;all under one&#xA0;roof!&#x201D;</figcaption></figure><p>These seemed great at first, but we ended up hitting a lot of gotchas with how AutoScalingPlans interact with AutoScalingGroups, particularly where CloudFormation was concerned.</p><h2 id="tl-dr">TL;DR</h2><p>This article will interest you if you&#x2019;re considering moving to <strong>ScalingPlans</strong> and are a heavy user of <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html">AWS AutoScaling RollingUpdates</a>. And hopefully save you some time and heartache.</p><p>You may also have ended up here because you&#x2019;ve seen any of the below error messages as part of a CloudFormation update:</p><pre><code>UPDATE_ROLLBACK_IN_PROGRESS
The following resource(s) failed to update: [AutoScalingPlan].

UPDATE_FAILED
Scaling  plan has been updated but failed to be applied to all resources.  Problems were encountered for 1 resource. See scaling plan resources for  the failure details.</code></pre><p>And subsequently maybe also the following in the <strong>AWS AutoScaling</strong> console:</p><pre><code>ActiveWithProblems
The resource was not updated because it was found to have different min/max capacity than what the scaling instruction indicates</code></pre><p>Read on to find out more. Spoiler alert: <em>there&#x2019;s no happy ending</em>.</p><h2 id="getting-started">Getting Started</h2><p>On the face of it, moving to <strong>ScalingPlans</strong> seemed simple. We aren&#x2019;t super-complicated people; we&#x2019;d implemented <strong>TargetTracking</strong> policies on some of our services to track and add/remove instances in accordance with our AutoScalingGroup&#x2019;s <strong>ASGTotalCPUUtilization</strong> metric. This old policy mapped perfectly to an equivalent <strong>ScalingInstruction</strong> <strong>TargetTrackingConfiguration</strong> property in the new resource. So we had feature-parity immediately.</p><p>Setting the <strong>PredictiveScalingMode</strong> to <strong>ForecastOnly</strong> allowed AWS Auto Scaling start to generate pretty looking graphs using <strong>Machine Learning&#x2122;</strong>!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-OLs4NHB5MW8IwNKH1G7gJg.png" class="kg-image" alt loading="lazy"><figcaption>You too might be able to be as efficient as AWS suggest you can&#xA0;be!</figcaption></figure><p>So, we set the <strong>PredictiveScalingMode </strong>to <strong>ForecastAndScale</strong>, basking in wonder at the <em>dozens</em> of <strong>AutoScaling ScheduledActions</strong> that were implemented on our <strong>AutoScalingGroup</strong>. Wow! Every hour, the size of the <strong>AutoScalingGroup</strong> would be enforced! You could even use the <strong>ScheduledActionBufferTime </strong>property to scale a set amount of time before the hour, in case you usually get really busy when the clock strikes 12, just like we do! Amazing!</p><p>For one reason or another, we went back to <strong>ForecastOnly</strong>. Our services are frequently victims to short, sudden, bursts of traffic. So the reality was that the capacity levels that AWS suggested was fine for regular traffic, but not enough if we saw an unexpected, unpredictable spike of traffic.</p><p>We just ended up setting our <strong>MinCapacity</strong> ever-higher until we were overprovisioned enough to deal with these spikes. This led to some ugly <strong>PredictiveScaling</strong> graphs&#x200A;&#x2014;&#x200A;the blue line in the image above was continually flatlined above the orange line. A constant reminder of &#x201C;wasted&#x201D; compute.</p><p>So yeah, we turned predictive scaling off, thinking that we&#x2019;d maybe enable it again sometime. We kept the new resources, because they were newer (hence better) and we still had the TargetTracking policies set up, so had feature parity, right? This was true, but we encountered some unexpected gotchas...</p><h2 id="changing-autoscalinggroup-capacity">Changing AutoScalingGroup Capacity</h2><p>We frequently want to change the size of our <strong>AutoScalingGroup</strong>s. In production, we might scale up for a once-off event where we expect greater traffic. In our performance testing environments, we want to scale down at the end of the day to save money.</p><p>We also try to be good systems engineers. That means keeping our AWS infrastructure definitions within CloudFormation templates, for all the good reasons that entails (maintainability, reviewability, recreatability, etc).</p><p>So when implementing the new ScalingPlans, we did so by removing our old <strong>AWS::AutoScaling::ScalingPolicy</strong> CloudFormation resources and replacing them with <strong>AWS::AutoScalingPlans::ScalingPlan</strong> resources. The latter requires that you provide a <strong>MinCapacity</strong> and a <strong>MaxCapacity</strong> for the ScalingPlan to enforce upon the AutoScalingGroup:</p><pre><code>&quot;AutoScalingPlan&quot;: {
      &quot;Type&quot; : &quot;AWS::AutoScalingPlans::ScalingPlan&quot;,
      &quot;Properties&quot; : {
        &lt;...snip...&gt;
        &quot;ScalingInstructions&quot; : [ {
          &quot;DisableDynamicScaling&quot; : false,
          &quot;MaxCapacity&quot; : &lt;Integer&gt;,
          &quot;MinCapacity&quot; : &lt;Integer&gt;,
        &lt;...snip...&gt;
}</code></pre><p>This looked familiar, as we also set <strong>MinSize</strong> and <strong>MaxSize</strong> on our AutoScalingGroup resource using a couple of CloudFormation Parameters:</p><pre><code>&quot;AutoScalingGroup&quot;: {
      &quot;Type&quot;: &quot;AWS::AutoScaling::AutoScalingGroup&quot;,
      &quot;Properties&quot;: {
        &lt;...snip...&gt;
        &quot;MinSize&quot;: { &quot;Ref&quot;: &quot;MinSize&quot; },
        &quot;MaxSize&quot;: { &quot;Ref&quot;: &quot;MaxSize&quot; },
        &lt;...snip...&gt;
}</code></pre><p>We could just reuse those, implementing as so:</p><pre><code>&quot;AutoScalingPlan&quot;: {
      &quot;Type&quot; : &quot;AWS::AutoScalingPlans::ScalingPlan&quot;,
      &quot;Properties&quot; : {
        &lt;...snip...&gt;
        &quot;ScalingInstructions&quot; : [ {
          &quot;DisableDynamicScaling&quot; : false,
          &quot;MaxCapacity&quot; : { &quot;Ref&quot;: &quot;MinSize&quot; },
          &quot;MinCapacity&quot; : { &quot;Ref&quot;: &quot;MaxSize&quot; },
        &lt;...snip...&gt;
}</code></pre><p>Now, to change the number of instances running, it should be as simple as adjusting the value of <strong>MinSize</strong> and <strong>MaxSize</strong> CloudFormation Parameters. Let&#x2019;s do it!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-Kg3FcWRua3FNwhKdCb7W7A.png" class="kg-image" alt loading="lazy"><figcaption>Wait, what? At least&#x2026; it&#x2019;s&#x2026; not..&#xA0;red?</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-TAuUxXNY4qdR347TdYJ5GA.png" class="kg-image" alt loading="lazy"><figcaption>I think CloudFormation just told me what it really thinks of&#xA0;me.</figcaption></figure><p>The above images show result of adjusting either of the shared <strong>MinSize</strong> or <strong>MaxSize</strong> CloudFormation parameter values and then performing a CloudFormation update. What happens seems to be the following:</p><ol><li>The <strong>AutoScalingPlan</strong> resource transitions into <strong>UPDATE_IN_PROGRESS</strong>.</li><li>In the AWS AutoScaling console, the <strong>ScalingPlan</strong> reports that it is:&#xA0;<br><em><strong>ActiveWithProblems&#x200A;</strong></em><em>&#x2014;&#x200A;The resource was not updated because it was found to have different min/max capacity than what the scaling instruction indicates.</em></li><li>The <strong>AutoScalingPlan</strong> resource transitions into <strong>UPDATE_FAILED</strong> and the CloudFormation Stack begins to roll back.</li><li>When the stack enters <strong>ROLLBACK_COMPLETE</strong>, the <strong>MinSize</strong>/<strong>MaxSize</strong> parameter you changed is set back to its original value. The size of the <strong>AutoScalingGroup</strong> also hasn&#x2019;t changed.</li></ol><p>This is not great&#x200A;&#x2014;&#x200A;let&#x2019;s see what AWS has to say on the matter in the <strong>Other Considerations</strong> in the <a href="https://docs.aws.amazon.com/autoscaling/plans/userguide/gs-best-practices.html#gs-considerations">Best Practices for AWS Auto Scaling Plans</a> documentation&#x2026;</p><blockquote>Your customized settings for minimum and maximum capacity, along with other settings used for dynamic scaling, show up in other consoles. However, we recommend that after you create a scaling plan, you <strong>do not modify these settings from other consoles </strong>because your scaling plan does not receive the updates from other consoles.</blockquote><p>Emphasis mine. If we go back to our <strong>CloudFormation Events </strong>above we can see that the <strong>AutoScalingGroup</strong> resource was updated by CloudFormation before the <strong>AutoScalingPlan</strong> resource was. That&#x2019;ll be a modification from an<em> &#x201C;other console&#x201D;</em>, then.</p><h2 id="sound-of-separation">Sound of Separation</h2><p>OK, fine. It&#x2019;s a bit messy, but we can use seperate CloudFormation parameters:</p><ul><li><strong>AutoScalingGroupMinSize</strong>: MinSize of the AutoScalingGroup resource</li><li><strong>AutoScalingGroupMaxSize</strong>: MaxSize of the AutoScalingGroup resource</li><li><strong>AutoScalingPlanMinSize</strong>: MinCapacity of the AutoScalingPlan resource</li><li><strong>AutoScalingPlanMaxSize</strong>: MinCapacity of the AutoScalingPlan resource</li></ul><p>We can&#x2019;t omit any; <strong>MinSize</strong> and <strong>MaxSize</strong> are <strong>Required</strong> according to the <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html">AWS::AutoScaling::AutoScalingGroup</a> documentation. Same deal for <strong>MinCapacity</strong> and <strong>MaxCapacity</strong> of <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-autoscalingplans-scalingplan-scalinginstruction.html">AWS::AutoScalingPlans::ScalingPlan </a>ScalingInstructions.</p><p>We&#x2019;ll just set <strong>AutoScalingGroupMinSize</strong> and <strong>AutoScalingGroupMaxSize</strong> to 0, then scale up our service by adjusting <strong>AutoScalingPlanMinSize</strong> and <strong>AutoScalingPlanMaxSize</strong>. That works just fine.</p><p>At face value, this has fixed our issue. We can now adjust <strong>AutoScalingPlanMinSize </strong>and<strong> AutoScalingPlanMaxSize</strong> freely. This updates the size of the <strong>AutoScalingGroup</strong> without adjusting it via CloudFormation.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-ZnMEp36fIYGFuab_JuZ-BA.png" class="kg-image" alt loading="lazy"><figcaption>Aww yiss.</figcaption></figure><p><em>&#x201C;And they all lived happily ever after&#x201D;</em>, right? Here&#x2019;s where I tell you the follow-up story of how we&#x2019;re <em>heavy</em> users of <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html">AWS AutoScaling RollingUpdates</a>. When we deploy a new version of a service, it looks like this:</p><ul><li>We create a new <strong>Amazon Machine Image (AMI)</strong> which contains a new version of our application.</li><li>We update our <strong>AWS::AutoScaling::LaunchConfiguration</strong> resource with this new AMI ID as part of a CloudFormation stack update. This forces a recreation of the <strong>LaunchConfiguration</strong> resource which in turn cascades an update to the <strong>AutoScalingGroup</strong> resource.</li><li>Our <strong>AutoScalingGroup</strong> has an <strong>UpdatePolicy</strong> configured to perform <strong>RollingUpdates</strong>. Replacing the <strong>LaunchConfiguration</strong> triggers the replacement of each one of the <strong>AutoScalingGroup</strong>&#x2019;s running instances, replacing the old instances with new instances. When the <strong>RollingUpdate </strong>is complete, so is our deployment.</li></ul><p><strong>RollingUpdates</strong> and <strong>ScalingPlans</strong> appear to co-exist, in that you won&#x2019;t see a <strong>RollingUpdate</strong> fail. It&#x2019;ll succeed just fine. Then, when next you come to adjust the size of your <strong>AutoScalingGroup </strong>via the <strong>AutoScalingPlanMinSize</strong> and <strong>AutoScalingPlanMaxSize </strong>parameters, CloudFormation will scream at you:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/AWS-AutoScalingPlans--RollingUpdates-and-CloudFormation/1-Pj29VS42QtL2EPxbHE85bg.png" class="kg-image" alt loading="lazy"><figcaption>Why do you hate me, ScalingPlan?</figcaption></figure><blockquote><strong>UPDATE_FAILED</strong><br>Scaling plan has been updated but failed to be applied to all resources. Problems were encountered for 1 resource. See scaling plan resources for the failure details.</blockquote><p>It&#x2019;ll rollback immediately. If you&#x2019;re quick, you&#x2019;ll see the same status messages in the <strong>AWS AutoScaling</strong> console from when we tried using the shared <strong>MinSize</strong>/<strong>MaxSize</strong> parameters:</p><blockquote><strong>ActiveWithProblems&#x200A;</strong><em>&#x2014;&#x200A;</em>The resource was not updated because it was found to have different min/max capacity than what the scaling instruction indicates.</blockquote><p>What happened here? Well, during the first stack update where we performed the<strong> RollingUpdate</strong>, I observed the following:</p><ul><li>Before doing anything, Note the size of your <strong>AutoScalingGroup</strong>. It should match <strong>AutoScalingPlanMinSize </strong>and<strong> AutoScalingPlanMaxSize</strong>.</li><li>Start a CloudFormation update that triggers a <strong>RollingUpdate </strong>(e.g. alter the AMI ID of the <strong>LaunchConfiguration</strong>). Watch it succeed.</li><li>Note the size of your <strong>AutoScalingGroup</strong> at this point. You&#x2019;ll notice that it has changed to match <strong>AutoScalingGroupMinSize</strong> and <strong>AutoScalingGroupMaxSize</strong>.</li></ul><p>This is unexpected. And confirms that a <strong>RollingUpdate</strong> will reapply whatever properties your CloudFormation template&#x2019;s <strong>AutoScalingGroup</strong> has set.</p><p>The next time you come along to adjust the capacity of your <strong>AutoScalingGroup </strong>(be that in five minutes or in five months) by adjusting <strong>AutoScalingPlanMinSize</strong> or <strong>AutoScalingPlanMaxSize</strong>, you&#x2019;ll be set up to fail from the get-go.</p><p>As far as the <strong>AutoScalingPlan</strong> is concerned, your <strong>AutoScalingGroup</strong> has changed from underneath it. From an &#x201C;<em>other console&#x201D;</em>, as the <a href="https://docs.aws.amazon.com/autoscaling/plans/userguide/gs-best-practices.html#gs-considerations">Best Practices for AWS Auto Scaling Plans</a> liked to put it.</p><p>The <strong>AutoScalingPlan</strong> is never updated about this change, and will never automatically fix itself. In practice, the only way to fix this is to either:</p><ul><li>Manually adjust the size of the <strong>AutoScalingGroup</strong> back to the same values as the last known adjustment that the <strong>AutoScalingPlan</strong> made.</li></ul><p>or</p><ul><li>Delete and recreate the <strong>ScalingPlan</strong> resource.&#xA0;<br>(Note: when you do this, your <strong>AutoScalingGroup</strong> size will revert to <strong>AutoScalingGroupMinSize</strong> and <strong>AutoScalingGroupMaxSize.&#xA0;</strong><br>Sure hope you hadn&#x2019;t configured those as 0&#x2026;)</li></ul><p>Speaking as a guy who&#x2019;s deleted a few dozen <strong>ScalingPlans</strong> to fix these issues, let me tell you that deleting them is what you&#x2019;ll be doing most often.</p><p>Why? Well, while you <em>can</em> determine what the current size of an <strong>AutoScalingGroup</strong> is, and while you <em>can</em> determine what the current capacities of an <strong>AutoScalingPlan</strong> are, you <em>cannot</em> determine what an <strong>AutoScalingPlan</strong> <em>thinks</em> that the <strong>AutoScalingGroup</strong> should be set to. That information is not exposed to us mere mortals.</p><h2 id="conclusions">Conclusions</h2><p>It&#x2019;s a tale as old as time&#x200A;&#x2014;&#x200A;new AWS services, existing AWS services and CloudFormation don&#x2019;t always mix or fit tightly enough together. In this case, there&#x2019;s a lack of integration between <strong>AutoScalingGroups</strong> and <strong>AutoScalingPlans</strong>, and an unreasonable expectation that <strong>AutoScalingPlans </strong>should be the only resources permitted to change the size of an <strong>AutoScalingGroup</strong>.</p><p>The root cause of our problem is that CloudFormation always rolls back. In reality, all we&#x2019;d need to fix our issue is one additional property named <strong>ForceCurrentCapacity</strong> on an <strong>AWS::AutoScalingPlans::ScalingPlan ScalingInstruction</strong> resource. When true, the <strong>AutoScalingPlan</strong> could say: &#x201C;yeah, I&#x2019;m gonna enforce whatever my <strong>MinCapacity</strong> and <strong>MaxCapacity</strong> are on the <strong>AutoScalingGroup</strong>, regardless or not whether they seem to have changed&#x201D;.</p><p>But we don&#x2019;t have that. And instead, due to continued rollbacks while trying to adjust the size of our <strong>AutoScalingGroup</strong> resources following service deployments, we reluctantly went back to <strong>AutoScaling::ScalingPolicy </strong>resources. Although they&#x2019;re not as fancy, they work without any real fuss and scale our services when we need it.</p><p>And while we don&#x2019;t have the option of turning <strong>PredictiveScaling</strong> back on or seeing the pretty graphs that the <strong>AWS Auto Scaling</strong> console provides, I&#x2019;ll take working deployments and predictable CloudFormation updates any day.

</p>]]></content:encoded></item><item><title><![CDATA[Declarative Jenkinsfiles: Dynamic Parallel Stages]]></title><description><![CDATA[Been working on replacing a crusty old Jenkins job recently that’s used for scaling up a load-testing environment. Currently, it’s a good…
]]></description><link>https://blog.hybby.net/declarative-jenkinsfiles--dynamic-parallel-stages/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb0</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Wed, 29 Apr 2020 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Been working on replacing a crusty old Jenkins job recently that&#x2019;s used for scaling up a load-testing environment. Currently, it&#x2019;s a good ol&#x2019; handcranked freestyle job with a 300-line bash script pasted into the <strong>Execute Shell</strong> field. When it needs debugged or changed it&#x2019;s fun for all the family.</p><p>I&#x2019;ve wanted to rewrite this for a while. Right now, the script has a bunch of configuration at the top of the file which dictates how many instances to launch, what sizes of instances to use, etc. And it&#x2019;s pretty repetitive:</p><pre><code># config for standard load tests
SERVICEONE_NORMALLOAD_INSTANCETYPE=c5.xlarge
SERVICEONE_NORMALLOAD_MINSIZE=3
SERVICEONE_NORMALLOAD_MAXSIZE=3
SERVICETWO_NORMALLOAD_INSTANCETYPE=c5.large
SERVICETWO_NORMALLOAD_MINSIZE=6
SERVICETWO_NORMALLOAD_MAXSIZE=6

# config for high load tests
SERVICEONE_HIGHLOAD_INSTANCETYPE=c5.xlarge
SERVICEONE_HIGHLOAD_MINSIZE=6
SERVICEONE_HIGHLOAD_MAXSIZE=6
SERVICETWO_HIGHLOAD_INSTANCETYPE=c5.large
SERVICETWO_HIGHLOAD_MINSIZE=9
SERVICETWO_HIGHLOAD_MAXSIZE=9

if [[ $SCALING_LEVEL = &apos;NORMAL&apos; ]] ; then
    # perform scaling actions with the first set of config...
    ...

elif [[ $SCALING_LEVEL = &apos;HIGH&apos; ]] ; then
    # perform scaling actions with the second set of config...
    ...

fi</code></pre><p>We don&#x2019;t have just two services, of course. Or just two load settings. So you can see how this script gets pretty gnarly, pretty quickly. Adding a new service? Sure hope you got all the variable names right. Something not working? Better step through every single command to see what&#x2019;s not as you expected.</p><p>Additionally, this script works through every service sequentially. This increases the time to scale the environment drastically if you have a bunch of services. The total time taken is the product of the number of services you have, and how long it takes to scale a service. Worse if one takes longer than another. There are no actions performed in parallel.</p><p>How could I make this better? For config, I envisioned something like this:</p><pre><code>def SERVICES = [
    &quot;service-1&quot;: [
        &quot;NORMAL&quot;: [ 
            &quot;InstanceType&quot;: &quot;c5.xlarge&quot;, 
            &quot;MinSize&quot;: 3, 
            &quot;MaxSize&quot;: 3
        ],
        &quot;HIGH&quot;: [
            &quot;InstanceType&quot;: &quot;c5.xlarge&quot;, 
            &quot;MinSize&quot;: 6, 
            &quot;MaxSize&quot;: 6
        ]
    ],
    &quot;service-2&quot;: [
        &quot;NORMAL&quot;: [ 
            &quot;InstanceType&quot;: &quot;c5.large&quot;, 
            &quot;MinSize&quot;: 6, 
            &quot;MaxSize&quot;: 6
        ],
        &quot;HIGH&quot;: [
            &quot;InstanceType&quot;: &quot;c5.large&quot;, 
            &quot;MinSize&quot;: 9, 
            &quot;MaxSize&quot;: 9
        ]
    ]
]</code></pre><p>Should be achievable in a <a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=1&amp;cad=rja&amp;uact=8&amp;ved=2ahUKEwjW_Z-4iI7pAhWPiFwKHbNbCyAQFjAAegQIAxAB&amp;url=https%3A%2F%2Fjenkins.io%2Fdoc%2Fbook%2Fpipeline%2Fsyntax%2F&amp;usg=AOvVaw0RQhrSz2PNbCo-OvxK9F54">Jenkins Declarative Pipeline</a>, right? It&#x2019;s easy enough to define a list of nested maps. By doing this, I can set all of my configuration values at the top of my Jenkinsfile, once per service. And people can come along and easily see what&#x2019;s set to what. Great.</p><p>It got me thinking though. Every service scales up in the same way. We update a <strong>Launch Configuration</strong> and an <strong>Autoscaling Group</strong> resource in a <a href="https://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;source=web&amp;cd=11&amp;cad=rja&amp;uact=8&amp;ved=2ahUKEwj-39LOiI7pAhXCQkEAHbGHDRkQFjAKegQIBBAB&amp;url=https%3A%2F%2Fdocs.aws.amazon.com%2FAWSCloudFormation%2Flatest%2FUserGuide%2Fcfn-whatis-concepts.html&amp;usg=AOvVaw2__8bW_fGKshP2eE9eajkD">CloudFormation Stack</a>. Do we have to do this sequentially? Can we kick off each service&#x2019;s scaling job in parallel to save time?</p><p>I ended up doing just this, implementing the parallelism with <a href="https://www.jenkins.io/blog/2017/09/25/declarative-1/">Parallel Stages</a> in my Jenkinsfile. However, instead of writing out each stage individually, I wrote a function that generated one stage per item in my configuration map. Then, I could use the parallel directive to kick off every stage at once, at the same time.</p><p>My Jenkinsfile looked something like this:</p><pre><code>// set up a config map as per the above snippet
def SERVICES = [ ...see above... ] 

// generate a pipeline stage for each item in the config map
def parallelStagesMap = SERVICES.collectEntries {
    [ &quot;${it}&quot;: generateStage(it) ]
}

// here&apos;s how we generate identical stages
def generateStage(service) {
    def service_name = service.key
    return {
        stage(&quot;Service: ${service_name}&quot;) {
            size = service.value[env.LEVEL][&apos;InstanceType&apos;]
            max = service.value[env.LEVEL][&apos;MaxSize&apos;]
            min = service.value[env.LEVEL][&apos;MinSize&apos;]

            // insert your scaling commands in here
            sh( script: &quot;&quot;&quot;
                    echo &quot;Action: ${env.LEVEL}&quot;    &amp;&amp; \
                    echo &quot;Service: ${service_name}&quot; &amp;&amp; \
                    echo &quot;InstanceType: ${size}&quot; &amp;&amp; \
                    echo &quot;MaxSize: ${max}&quot; &amp;&amp; \
                    echo &quot;MinSize: ${min}&quot;
                &quot;&quot;&quot;
            )
        }
    }
}

pipeline {
    agent any

    parameters {
        choice(
            name: &apos;LEVEL&apos;,
            description: &apos;What level do you want to scale to?&apos;,
            choices: [
                &apos;NORMAL&apos;,
                &apos;HIGH&apos;
            ]
        )
    }
    stages {
        stage(&apos;Scale services&apos;) {
            steps {
                script {
                    // kick off all the stages we generated
                    parallel parallelStagesMap
                }
            }
        }
    }
}</code></pre><p>When we run the job, it works and looks great:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Declarative-Jenkinsfiles--Dynamic-Parallel-Stages/1-R73-1zTz7aJHnMCn4xhxZw.png" class="kg-image" alt loading="lazy"><figcaption>Each item in our configuration hash has its own stage, executed in parallel.</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Declarative-Jenkinsfiles--Dynamic-Parallel-Stages/1-WOFmpBFykmBHYiImVcLy9A.png" class="kg-image" alt loading="lazy"><figcaption>Or if you prefer the fancier BlueOcean pipeline&#xA0;view&#x2026;</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Declarative-Jenkinsfiles--Dynamic-Parallel-Stages/1-C6FoqGsAlnlaFxgu2sMgkA.png" class="kg-image" alt loading="lazy"><figcaption>The logs for each stage are pretty useful&#xA0;too.</figcaption></figure><p>This method is especially useful if one particular service has a problem scaling up&#x200A;&#x2014;&#x200A;the error will show clearly on the stage related to that service, not halfway down a shell script somewhere that you have to dig into the logs of.</p><p>The configuration hash at the top also serves as self-documentation for our scaling levels. How many levels do we scale to? Oh, there&#x2019;s two levels defined in the hash. How many services do we scale? They&#x2019;re all there, unobscured and easy to see. No hidden settings, or secret numbers.</p><p>When our scaling levels change due to a projected increase in traffic or otherwise, we&#x2019;re now one Pull Request away from having the updated levels ready-to-go&#x200A;&#x2014;&#x200A;peer reviewed, merged once. New service launched? Cool, raise a PR. No more need to hunt through multiple Jenkins jobs, wondering which one is live and which one isn&#x2019;t. The next run of the job will pull in the latest Jenkinsfile, along with the latest config.</p><p>This also opens us up to being able to schedule the scaling up and scaling down of our load testing environment using the <a href="https://github.com/jenkinsci/parameterized-scheduler-plugin">Parameterized Scheduler Plugin</a> and the ubiquitous cron syntax. In the pipeline section of our Jenkinsfile, we can set something like this:</p><pre><code>triggers {
    parameterizedCron(&apos;&apos;&apos;
        # scale up in the morning, scale down at night (weekdays)
        H 6 * * 1-5 % LEVEL=NORMAL        
        H 18 * * 1-5 % LEVEL=ZERO
       &apos;&apos;&apos;)
    }</code></pre><p>This takes the legwork out of manually making sure the environment is available each morning and ensures we&#x2019;re saving as much as we can on instance costs during evenings and weekends. We also don&#x2019;t have to mess around with AutoScalingGroup ScheduledActions, which is nice.</p><p>You can see some extremely abstract versions of Jenkinsfiles I&#x2019;ve been working on recently at my <a href="https://github.com/hybby/jenkinsfiles">GitHub</a>, recorded because I realise it&#x2019;s probably not the last time I&#x2019;ll need to write something like this.</p><p>Happy Jenkinsfile-ing!

</p>]]></content:encoded></item><item><title><![CDATA[Macs, Control Characters, and iTerm2]]></title><description><![CDATA[
I always forget how to do this. And I always lose Brad Erickson’s excellent article that taught me how to fix it. So here it is, for…
]]></description><link>https://blog.hybby.net/macs--control-characters--and-iterm2/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedaf</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Tue, 31 Mar 2020 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I always forget how to do this. And I always lose <a href="http://eosrei.net/articles/2011/07/ascii-control-characters-escape-sequences-iterm">Brad Erickson&#x2019;s excellent article</a> that taught me how to fix it. So here it is, for posterity.</p><p>Whenever I switch to a new Mac, be that through replacement of machine or job, I tend to wrestle with my laptop for a while. Mostly, this is due to Mac systems using the <strong>Command </strong>(&#x2318;) key for shortcuts (copy, paste, etc.) while my favoured terminal emulator, <strong>iTerm2</strong>, sensibly uses <strong>Ctrl (^)</strong> as the modifier key for sending control characters (<strong>^C</strong>, etc).</p><p>I&#x2019;m also a stubborn critic of modern laptop keyboard layouts in general. I don&#x2019;t <em>want</em> the bottom-left key on my laptop to be <strong>Fn</strong>. I want it to be <strong>Ctrl</strong>, like how it was when I grew up, dammit! So of course, I use an external keyboard which has everything laid out the way I like it.</p><p>So, in summary, I want my keyboard to do the following, for example:</p><ul><li>Use <strong>Ctrl + c </strong>to copy in most applications.</li><li>Use <strong>Ctrl + c </strong>to send control characters in iTerm2.</li></ul><p>This presents a small challenge, because these are different things. One uses by default, <strong>Command </strong>(&#x2318;). The other uses<strong> Ctrl (^)</strong>.</p><h3 id="fix-one-thing-break-another">Fix one thing, break&#xA0;another</h3><p>When I first connect my external keyboard to a new Mac, copying, pasting, etc. requires that I use <strong>Super (&#x201C;Windows&#x201D;) + c</strong> for copy, for example. But <strong>iTerm2</strong> works perfectly as I expect it to. No good.</p><p>I know what you&#x2019;re thinking. Just go to <strong>System Preferences</strong> &#x2192; <strong>Keyboard</strong> &#x2192; <strong>Modifier Keys</strong>&#x2026; and make sure you select the correct keyboard from the drop-down box, then swap the keys around like this:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Macs--Control-Characters--and-iTerm2/1-Z2hyvqZ-nZ9QKWjSU2PS2Q.png" class="kg-image" alt loading="lazy"><figcaption>This should work,&#xA0;right?</figcaption></figure><p>This fixes my first requirement. I can now copy, paste, etc. with what my keyboard considers <strong>Ctrl</strong>. But it&#x2019;s broken the second one! Now I need to use Super <strong>(&#x201C;Windows&#x201D;) + c</strong> to send a control character via <strong>iTerm2</strong>. Oh no!</p><h3 id="is-there-a-setting-for-that">Is there a setting for&#xA0;that?</h3><p>How about the <strong>iTerm2</strong> &#x2192; <strong>Preferences</strong> &#x2192; <strong>Keys </strong>&#x2192; <strong>Remap Modifiers</strong> menu?</p><p>As soon as you start playing with this, <strong>iTerm2</strong> asks for <strong>Accessibility Access (Events)</strong> privileges on your laptop.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Macs--Control-Characters--and-iTerm2/1-0uZljmoxSbsNJo2Sl5lF8g.png" class="kg-image" alt loading="lazy"><figcaption>Okay.</figcaption></figure><p>If you grant this, you&#x2019;re presented with a menu that looks suspiciously familiar to the one that we messed around with earlier:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Macs--Control-Characters--and-iTerm2/1-UMtXj9tHGVmgnZ53VxId0Q.png" class="kg-image" alt loading="lazy"><figcaption>This looks promising, but ultimately proves&#xA0;futile.</figcaption></figure><p>For one, this is a bit of a mind-melter for me. What if I&#x2019;ve adjusted the <strong>System Preferences</strong> menu from earlier? Does this override those? Perform another remap on top? Something else? My brain hurts just thinking about it.</p><p>For two, no matter what I adjust here, it doesn&#x2019;t actually <em>do</em> anything to my external keyboard. I&#x2019;m left to assume that this menu is hard-coded to adjust settings for the laptop&#x2019;s built-in keyboard, as it seems to be lacking the <strong>Select keyboard</strong> drop-down box from the earlier menu.</p><p>Let&#x2019;s not mess around with this, and let&#x2019;s find another solution.</p><h3 id="iterm2-key-bindings-to-the-rescue-">iTerm2 Key Bindings to the&#xA0;rescue!</h3><p>Brad Erickson seems to be the only other person on the entire Internet that this behaviour frustrates, and he wrote a <a href="http://eosrei.net/articles/2011/07/ascii-control-characters-escape-sequences-iterm">great article</a> on how to fix it using <strong>iTerm2</strong>&#x2018;s <strong>Key Bindings</strong> configuration.</p><p>Here&#x2019;s how I got it working to my liking:</p><p>Hit up <strong>iTerm2</strong> &#x2192; <strong>Preferences</strong> &#x2192; <strong>Keys</strong>. The default tab in this window is <strong>Key Bindings. </strong>In the bottom left corner, press the small <strong>plus sign (+) </strong>to add a new binding. That&#x2019;ll bring up a menu asking you for a shortcut and an action.</p><p>Here&#x2019;s where I press <strong>Ctrl + c</strong>, which now shows up as<strong> Command + c </strong>(<strong>&#x2318;c</strong>) after my previous adventures in <strong>System Preferences</strong>. As for an action, I want to <strong>Send Hex Code</strong>. Then, I can use Wikipedia&#x2019;s handy <a href="https://en.wikipedia.org/wiki/ASCII#Control_characters">ASCII Control Characters</a> table to select the hex code that represents the control character that I want to send. I want <strong>^C</strong>, which has a hex code of <strong>03</strong>, formatted properly as <strong>0x3</strong> as far as <strong>iTerm2</strong> is concerned. Turns out this control character is actually called the <strong>End-of-Text</strong> character. TIL.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.hybby.net/content/images/downloaded_images/Macs--Control-Characters--and-iTerm2/1-KqXYKKXFIW8AlmGvwqufzw.png" class="kg-image" alt loading="lazy"><figcaption>Configuring a Key Binding to send the ^C control character in&#xA0;iTerm2</figcaption></figure><p>As soon as the <strong>OK</strong> button is pressed, my shell&#x2019;s behaviour is as I expect.</p><h3 id="how-about-other-control-characters">How about other control characters?</h3><p>You can use the same mechanism to add the control characters that you send most often. Over a year and half, I&#x2019;ve found that I only commonly use these:</p><ul><li><strong>^C (0x3)</strong>: Ctrl + c (End-of-Text, or &#x201C;send interrupt&#x201D;)</li><li><strong>^D (0x4)</strong>: Ctrl + d (End-of-Transmission, or &#x201C;logout&#x201D;)</li><li><strong>^Z (0x1a)</strong>: Ctrl + z (Substitute, or &#x201C;background job&#x201D;)</li><li><strong>^] (0x1d)</strong>: Ctrl + ] (Group Separator, or &#x201C;exit telnet&#x201D;)</li></ul><p>I&#x2019;ll be the first to admit that setting these up manually isn&#x2019;t ideal, but given the infrequency with which I move machines, I&#x2019;m happy to take the hit since it&#x2019;s truly a &#x201C;set and forget&#x201D; solution.</p><p>Hopefully this will help someone out there. And hopefully now you can <em>control</em> your terminal properly and <em>escape</em> any stress that this situation was putting you under!

</p>]]></content:encoded></item><item><title><![CDATA[Timeouts Loading Data from S3 to Aurora]]></title><description><![CDATA[
Been working on a data migration recently which involves loading a pretty big tab-separated file into an Amazon Aurora (MySQL) database and…
]]></description><link>https://blog.hybby.net/timeouts-loading-data-from-s3-to-aurora/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb9</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Fri, 20 Apr 2018 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Been working on a data migration recently which involves loading a pretty big tab-separated file into an Amazon Aurora (MySQL) database and hit an interesting issue in one of our sub-accounts.</p><p>We&#x2019;d gone through the documented steps of creating an IAM Role, associating its ARN with the cluster using aws rds add-role-to-db-cluster and adding its ARN as the value of the aurora_load_from_s3_role DB cluster parameter.</p><p>We&#x2019;d added the s3:GetObject and s3:ListBucket permissions to the bucket policy of the data source, using the IAM Role&#x2019;s ARN as the principal (we decided to use a bucket policy for ease of clean-up after the migration had been completed).</p><p>But still, no matter what we tried, our LOAD DATA FROM S3 FILE query would <em>always</em> hang for approximately five minutes and then throw us an error:</p><pre><code>ERROR 1815 (HY000): Internal error: Unable to initialize S3Stream</code></pre><p>What was going on? It felt like a timeout, due to the severe hang before the command bombed out. Checking our tables afterwards verified that no data had been written by the query, either.</p><p>An extra-strange thing is that the query worked just fine in our main AWS account (using a different S3 bucket and Role ARN, but with otherwise functionally identical configuration).</p><p>We only experienced the issue when trying to run the query from one of our sub-accounts, which we use for performance testing.</p><p>Double and triple checking our S3 and IAM setups didn&#x2019;t flag up anything obvious. So we delved into the <a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/AuroraMySQL.Integrating.LoadFromS3.html">documentation</a>, and the most interesting thing appeared to be the fifth point:</p><blockquote>Configure your Aurora MySQL DB cluster to allow outbound connections to Amazon S3.</blockquote><p>Checking the page page linked to in this section implied that our cluster was likely to be misconfigured if we encountered an error like so:</p><pre><code>ERROR 1873 (HY000): Lambda API returned error: Network Connection. Unable to connect to endpoint</code></pre><p>However, this didn&#x2019;t seem to be the case for us. We weren&#x2019;t seeing this error. Still, my instinct was that we were hitting some sort of network problem so I decided to check our our VPC Endpoint configuration in the console, accessed via <em>VPC -&gt; Endpoints</em>.</p><p>Sure enough, I didn&#x2019;t have a VPC Endpoint configured for the S3 service in this account. Checking our main account showed that we <em>did</em> have an S3 endpoint, which gave another hint as to why it worked there.</p><p>A<em> VPC Endpoint </em>is a resource within AWS that allows you to privately connect a VPC to a particular AWS service. This means that services which are normally hosted in private subnets, like database clusters can be permitted to communicate with AWS services that expect to be communicated with via the Internet, like Amazon S3. Configuring a VPC Endpoint inserts a statement into Routing Tables that you specify, meaning you can control exactly what subnets you are granting this access to.</p><p>I created a new endpoint, selecting com.amazonaws.us-east-1.s3 as the Service Name. I provided the VPC ID where my Amazon Aurora cluster was located. Finally, I provided the Route Table IDs that contained the subnets used by my Amazon Aurora cluster, and decided to use the default policy that was provided.</p><p>After creating the endpoint I reran the LOAD DATA FROM S3 query. Our query hit five minutes and didn&#x2019;t bomb out like before. The anticipation started to build&#x200A;&#x2014;&#x200A;our datafile was rather large (approximately 13m rows), so we expected it to take a bit of time. Then, finally:</p><pre><code>Query OK, 12537768 rows affected (10 min 50.39 sec)
Records: 12537768 Deleted: 0 Skipped: 0 Warnings: 0</code></pre><p>It worked! And it was pretty damn fast, too.</p><p>Lesson learnt: even if you <em>really, really</em> hope the problem isn&#x2019;t your VPC configuration, sometimes it is. That, and sometimes you need to use a bit of imagination with AWS&#x2019;s documentation.</p><p>Hopefully I&#x2019;ll get to write a bit more about my experiences performing data migrations in AWS soon; in particular we&#x2019;ve been having fun exporting data from Amazon Redshift to S3, where we then import that data into an Amazon Aurora cluster. I&#x2019;ve been really impressed with the simplicity and speed that can be achieved and hope never have to load data from anywhere else ever again.

</p>]]></content:encoded></item><item><title><![CDATA[Puppet CA Host Certificate Expiry]]></title><description><![CDATA[
So you read the title and thought “Hey, I just read that article!”, right? Nah, here’s another article about Puppet CA Server certificates…
]]></description><link>https://blog.hybby.net/puppet-ca-host-certificate-expiry/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb1</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Wed, 25 Oct 2017 00:01:00 GMT</pubDate><content:encoded><![CDATA[<p>So you read the title and thought <em>&#x201C;Hey, I just read that article!&#x201D;</em>, right? Nah, here&#x2019;s another article about Puppet CA Server certificates. Similar, but different. I&#x2019;m just being cheeky by submitting them a couple of hours apart.</p><h2 id="ca-certs-host-certs-what-">CA certs&#x2026; Host certs&#x2026;&#xA0;What?!</h2><p>So, when a normal node runs Puppet for the first time, it contacts its Puppet CA server for a certificate that proves it&#x2019;s allowed to be a client of that master. A detailed step through of what happens is:</p><ol><li>It generates a new SSL keypair ($::hostprivkey and $::hostpubkey)</li><li>Using its newly generated $::hostprivkey, it generates a certificate signing request ($::hostcsr) for a certificate and sends that off to whatever&#x2019;s configured as its Puppet CA server ($::ca_server). No catalog is compiled at this point, because the node isn&#x2019;t considered a trusted client of the Puppet master.</li><li>The CSR from the node ends up in the $::csrdir of the Puppet CA server. If the Puppet CA server is configured to allow auto-signing of the node that made the request, it signs it with its CA certificate ($::cacert) and pops the signed cert into $::signeddir. The CSR gets deleted.</li><li>On the node&#x2019;s next Puppet run, it gets a copy of the signed certificate from the Puppet CA server and is allowed to continue with its run. It gets a catalog and nice things happen to it for the rest of its little life. Or for five years. Which is how long the signed certs are valid for, by default.</li></ol><p>So we&#x2019;ve described a situation where a node requests what I&#x2019;ll call a <em>host certificate</em> from the CA, which gives one back signed by its <em>CA cert</em>.</p><h2 id="okay-okay-but-what-about-the-ca">Okay, okay. But what about the&#xA0;CA?</h2><p>You might already know this, but the Puppet CA server is usually a client of itself.</p><p>So when it was first spun up, the Puppet CA generated itself an SSL keypair ($::cakey and $::capub) and a CA certificate ($::cacert). This certificate is self-signed, and is marked as a CA cert, meaning it&#x2019;s allowed to sign other certificate requests.</p><p>That&#x2019;s on the master side. But Puppet masters also run the Puppet agent.</p><p>When the Puppet CA ran puppet agent -t for the first time, it went through the steps we went discussed above. Generated itself a new keypair ($::hostprivkey and $::hostpubkey)&#x2026; Looked to see who its CA server was&#x2026; (itself!) and submitted a CSR to them ($::hostcsr) and waited until it got a response back (from itself!).</p><p>So yeah, the upshot is that the Puppet CA server has two sets of certs. Its CA certs, and its host certs. We can illustrate that clearly here:</p><pre><code># puppet config print hostcert hostprivkey hostpubkey cacert cakey capub | awk &apos;{print $NF}&apos; | xargs md5sum
63c251f890893690df65f79d36eb5d62  /var/lib/puppet/ssl/certs/puppetca.example.com.pem
e2c7e9bb4b5a68c812989c2da702b971  /var/lib/puppet/ssl/private_keys/puppetca.example.com.pem
3b8874ae570cecb3470384b1fc307503  /var/lib/puppet/ssl/public_keys/puppetca.example.com.pem
f4288c68efa8a4f9bd07d0bac62710a6  /var/lib/puppet/ssl/ca/ca_crt.pem
af2f94391cefa3e8d296d0648ebeb5d5  /var/lib/puppet/ssl/ca/ca_key.pem
7c17a8d5cc920b1d923f4b18deefea51  /var/lib/puppet/ssl/ca/ca_pub.pem</code></pre><p>In the vast majority of cases, these certificates will have been generated at roughly the same time (usually the same day, almost certainly the same week). That means that they also <em>expire</em> at roughly the same time. But be warned, the content of the two certs is <em>completely</em> different, as the checksums above show!</p><p>So you might have went through the steps from my previous post, renewed your CA certs, managed to get everything working just nicely, perform your first Puppet run after the big scary change and be faced with this redness:</p><pre><code>Warning: Certificate &apos;puppetca.example.com&apos; will expire on 2017-11-22T22:08:37UTC</code></pre><p>Believe it or not, this is referring to the <em>host cert</em> of your Puppet CA cert. Check out the expiry of it if you don&#x2019;t believe me:</p><pre><code># openssl x509 -noout -text -in $(puppet config print hostcert) | grep -A2 Valid
        Validity
            Not Before: Nov 22 22:08:37 2017 GMT
            Not After : Nov 22 22:08:37 2022 GMT</code></pre><p>Fine, sounds legit. What&#x2019;s the impact?</p><p>Honestly? Not much. It means that once the &#x201C;Not After&#x201D; date has expired, the client cert used by your Puppet agent won&#x2019;t be accepted by the CA any more. That means no catalogues. Boo.</p><p>On nodes that aren&#x2019;t important, fixing this is a cinch. But not only is it a little bit embarrassing when your most important Puppet server can&#x2019;t be an agent of itself, it comes with some additional configurations. You can&#x2019;t just nuke $::ssldir and walk away like you usually would.</p><h2 id="what-s-the-big-deal-then">What&#x2019;s the big deal,&#xA0;then?</h2><p>Usually when you want to regenerate a Puppet client&#x2019;s certs you&#x2019;ll do this:</p><pre><code>root@client# rm -rf $(puppet config print ssldir)

root@puppetca# puppet cert clean client

root@client# puppet agent -t
&lt;keys regen&gt;
&lt;csr created&gt;
&lt;puppet ca signs&gt;
&lt;new cert recieved&gt;
&lt;puppet run continues&gt;</code></pre><p>This is a bad idea to run on your Puppet CA. Like, a <em>really</em> bad idea.</p><ol><li>It&#x2019;ll delete the entirety of your Puppet CA&#x2019;s SSL directory. That means your CA certs, CA keys and all of your estate&#x2019;s signed certificates gone, up in smoke. (Have I mentioned that it&#x2019;s a good idea to back this up?)</li><li>It&#x2019;s not so bad if you&#x2019;ve already deleted all of your SSL data, but if you were to run puppet cert clean puppetca when you still had all of your certs, you&#x2019;d revoke both your host certs <em>and </em>your CA certs. They&#x2019;d get appended to your CA&#x2019;s CRL and none of the nodes in your estate would be able to check in with your Puppet masters any more. This is because your Puppet CA would not believe its own certs are valid any more.</li></ol><p>So yeah, don&#x2019;t do any of that.</p><p>Instead, we can perform a bit of surgery on your Puppet CA&#x2019;s SSL directory and only remove the keys and certs that we mean to. We can even make it so that we can revert things if we mess it up, somehow! Let&#x2019;s go.</p><h2 id="alternative-dns-names">Alternative DNS&#xA0;Names</h2><p>First up, let&#x2019;s see what your CA thinks about its own certs:</p><pre><code>root@puppetca# puppet cert list --all | grep $(uname -n)
+ &quot;puppetca.example.com&quot;                                         (SHA256) A8:7A:68:3E:E9:61:CB:7A:FF:E9:42:48:FC:57:04:F8:BF:2E:05:8C:4D:EA:AF:F7:E5:3C:D6:5B:EF:8C:E1:6C (alt names: &quot;DNS:puppet&quot;, &quot;DNS:puppet-1.example.com&quot;, &quot;DNS:puppet.example.com&quot;)</code></pre><p>The alt names section tells me that the cert that&#x2019;s already issued has <a href="https://en.wikipedia.org/wiki/Subject_Alternative_Name">X.509 Subject Alternative Name</a> extensions baked in. This is a list of alternative DNS names that are also valid for the cert that&#x2019;s been signed. It&#x2019;s used so that your clients could hit, for example, a friendly CNAME DNS record that references your Puppet CA instead of the CA&#x2019;s FQDN itself.</p><p>We&#x2019;re gonna make sure that these are configured on your Puppet agent, too. Just so that we&#x2019;re replacing like-with-like.</p><pre><code>root@puppetca# puppet config print dns_alt_names

root@puppetca#</code></pre><p>If, like above, you don&#x2019;t have any output, you&#x2019;re missing a bit of config. dns_alt_names is be configured in /etc/puppet/puppet.conf under the [main] section. So add something appropriate like this:</p><pre><code>dns_alt_names=puppet,puppet-1.example.com,puppet.example.com</code></pre><p>Re-running puppet config print dns_alt_names should have more success now.</p><h2 id="back-it-up-back-it-up-">Back it up, back it&#xA0;up!</h2><p>Make sure and CYA by backing up everything you touch. And maybe even some stuff you don&#x2019;t. I went a bit overboard:</p><pre><code># create backup directories
mkdir -p $(puppet config print ssldir)/backup/{host,ca,signed}

# back up our ca certs and crls (local and ca) because we&apos;re paranoid
cp $(puppet config print cacert) $(puppet config print ssldir)/backup/ca/cacert.pem
cp $(puppet config print cacrl) $(puppet config print ssldir)/backup/ca/cacrl.pem
cp $(puppet config print localcacert) $(puppet config print ssldir)/backup/ca/localcacert.pem
cp $(puppet config print hostcrl) $(puppet config print ssldir)/backup/ca/hostcrl.pem

# back up our agent-side certs
cp $(puppet config print hostcert) $(puppet config print ssldir)/backup/host/hostcert.pem
cp $(puppet config print hostprivkey) $(puppet config print ssldir)/backup/host/hostprivkey.pem
cp $(puppet config print hostpubkey) $(puppet config print ssldir)/backup/host/hostpubkey.pem

# remove up our ca-side signed cert, backing it up ofc
cp $(puppet config print signeddir)/$(hostname -f).pem $(puppet config print ssldir)/backup/signed/signed.pem

# make sure they&apos;re there
find $(puppet config print ssldir)/backup | xargs ls -ld</code></pre><p>Now you should have a pretty list of files backed up in `$(puppet config print ssldir)/backup, should the worst happen.</p><h2 id="precision-cut">Precision cut</h2><p>Now for the action. For the avoidance of doubt, all of this is performed on your Puppet CA server.</p><pre><code># 1. stop puppet agent
service puppet stop 

# 2. move the host certs that Puppet Agent knows about
mv $(puppet config print hostcert) /tmp/hostcert.pem
mv $(puppet config print hostprivkey) /tmp/hostprivkey.pem
mv $(puppet config print hostpubkey) /tmp/hostpubkey.pem

# 3. move the signed version of the ca&apos;s host cert
mv $(puppet config print signeddir)/$(hostname -f).pem /tmp

# 4. regenerate our host ssl keypair, create csr and submit to ca
puppet agent -t

# 5. get the ca to sign our csr
puppet cert sign $(hostname -f)

# 6. get the agent to retrieve the signed cert and perform a run
puppet agent -t

# 7. restart puppet agent
service puppet start</code></pre><p>All going well, you should have new host certs on your Puppet CA server, with the actual CA certs themselves being left well alone!</p><p>Now, you can check the validity of your new host cert.</p><pre><code># openssl x509 -noout -text -in $(puppet config print hostcert) | grep -A2 Valid
        Validity
            Not Before: Oct 18 11:55:40 2017 GMT
            Not After : Oct 18 11:55:40 2022 GMT</code></pre><p>Compare it to the validity dates of your CA cert if you want to. They&#x2019;ll differ:</p><pre><code># openssl x509 -noout -text -in $(puppet config print cacert) | grep -A2 Valid
        Validity
            Not Before: Oct 18 10:22:30 2017 GMT
            Not After : Oct 18 10:22:30 2022 GMT</code></pre><p>Now we can hopefully put this sorry affair behind us. And get on with some real work wrangling some clouds, eh?</p><p>See you in five years!

</p>]]></content:encoded></item><item><title><![CDATA[Puppet CA Certificate Expiry]]></title><description><![CDATA[
Hard to believe it, but it’s coming up on five years since our company stood up its first Puppet master. No cake here. Instead, all we get…
]]></description><link>https://blog.hybby.net/puppet-ca-certificate-expiry/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb8</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Wed, 25 Oct 2017 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Hard to believe it, but it&#x2019;s coming up on five years since our company stood up its first <a href="https://puppet.com">Puppet</a> master. No cake here. Instead, all we get is a lousy old CA certificate expiry.</p><p>At first, I inhaled sharply and mentally prepared myself for the impending chaos of our client certificates becoming invalid.</p><p>Instead, I was pleasantly surprised when I stumbled across <a href="https://forge.puppet.com/puppetlabs/certregen">puppetlabs/certregen</a> which promised to make the whole sorry affair disappear painlessly. It&#x2018;s even Puppet 3 compatible!</p><p>The secret-sauce is all around the use of a clever <a href="https://puppet.com/blog/puppet-faces-what-heck-are-faces">Puppet Face</a> to provide the puppet certgen command on your CA server. This simplifies the regeneration of your master&#x2019;s CA cert to one command, creating a replacement from the exact same key used to create your expiring cert. The new cert&#x2019;s effectively a drop-in replacement!</p><p>The other piece of magic is a simple, super-portable class that manages the contents of $::localcacert on your client nodes to be the same as whatever&#x2019;s in your master&#x2019;s $settings::cacert file. It does this by calling the file()function on the Puppet master on catalog compilation, grabbing what the master believes its CA cert to be. So, we update the CA cert on the master and then the nodes take care of themselves!</p><p>Of course, because the replacement CA certificate is generated from the same keys as your expiring CA cert, this tool is <em>not suitable for use when your CA&#x2019;s keys have been compromised</em>.</p><p>So how does it work in practice? The module&#x2019;s <a href="https://github.com/puppetlabs/puppetlabs-certregen/blob/master/README.markdown">README</a> is pretty hot at explaining everything, but for the sake of completeness, here&#x2019;s the process I used. It assumes that your CA cert <em>hasn&#x2019;t expired yet</em>.</p><h2 id="install-the-module">Install the&#xA0;module</h2><p>We use Puppetfile, so I added the following to ours:</p><pre><code>mod &apos;puppetlabs-certregen&apos;, &apos;0.2.0&apos;</code></pre><h2 id="get-the-expiring-cert-s-serial">Get the expiring cert&#x2019;s&#xA0;serial</h2><p>Running puppet certregen ca won&#x2019;t do anything off the bat&#x200A;&#x2014;&#x200A;it&#x2019;ll error and spit out something like so:</p><pre><code>$ sudo puppet certregen ca
Error: The serial number of the CA certificate to rotate must be provided. If you are sure that you want to rotate the CA certificate, rerun this command with --ca_serial 01</code></pre><h2 id="actually-regenerate-the-ca-certificate">Actually regenerate the CA certificate</h2><p>When you&#x2019;re <em>for-reals</em> ready to regenerate your Puppet master&#x2019;s CA cert, re-run the above command providing the serial number you gleaned previously:</p><pre><code>$ sudo puppet certregen ca --ca_serial 01
Notice: Backing up current CA certificate to /var/lib/puppet/ssl/ca/ca_crt.1508247754.pem
Notice: Signed certificate request for ca
CA expiration is now 2022-10-16 13:42:34 UTC
CRL next update is now 2022-10-16 13:42:34 UTC</code></pre><p>As you can see, this backs up the current (expiring) CA cert, just in case. Then it spits out the new expiration date of its replacement. Great!</p><p>To be clear&#x200A;&#x2014;&#x200A;your CA server now has a new certificate, but all of the nodes in your Puppet deployment still have the old, expiring version. Let&#x2019;s fix that!</p><h2 id="enforce-node-management-of-ca-cert">Enforce node management of CA&#xA0;cert</h2><p>Now we&#x2019;ve gotta get some mechanism in place where your nodes can get the updated CA certificate. This is where the certregen::client class comes into play. It defines a File resource that manages each node&#x2019;s CA certificate (whose path is determined by consulting the $::localcacert variable).</p><p>We use Roles &amp; Profiles, so I added the following to our base profile:</p><pre><code>include certregen::client</code></pre><p>The important thing is that all of your nodes get this into their catalogs somehow, so you can use the main manifest or whatever.</p><p>This change needs to make its way into <em>all of the Puppet directory environments that you care about</em>. In my case, I made the change to our production environment and retroactively applied it to a handful of static environments that we care about. I consider pretty much every other environment as disposable.</p><h2 id="update-compile-masters">Update compile&#xA0;masters</h2><p>Got any non-CA, compile-only Puppet masters? Get them to complete a Puppet run against your CA as a priority. This&#x2019;ll update the CA used by these masters to the new one, along with the associated CRL.</p><pre><code>drew@pmaster-1 $ sudo puppet agent -t --server puppetca.example.com
...
Notice: /Stage[main]/Certregen::Client/File[/var/lib/puppet/ssl/certs/ca.pem]/content: content changed &apos;{md5}35d987adf79eaaed26780cd0e69f7d52&apos; to &apos;{md5}ed6cb1e53b7ad78ac41629fb8e3e2c38&apos;
Notice: /Stage[main]/Certregen::Client/File[/var/lib/puppet/ssl/crl.pem]/content: content changed &apos;{md5}910a663110f54bf974a26b750c3a8a90&apos; to &apos;{md5}a774a54175b68251b11d0b2e264eec37&apos;
...

Notice: Finished catalog run in 6.42 seconds</code></pre><p>If you don&#x2019;t do this first, any nodes that compile catalogs using these Puppet masters will get the CA cert that the compile master believes to be current&#x200A;&#x2014;&#x200A;in this case, the old, expiring one! And that&#x2019;s no good!</p><p>Don&#x2019;t have any compile masters? Then you&#x2019;re good.</p><h2 id="update-ca-cert-on-client-nodes">Update CA cert on client&#xA0;nodes</h2><p>As the nodes across your deployment check in with your Puppet masters, you&#x2019;ll notice their CA certificate and associated CRL update to the new one:</p><pre><code>drew@host1 $ sudo grep &apos;content changed&apos; /var/log/syslog | grep &apos;Certregen::Client&apos;
Oct 17 04:06:59 host1 puppet-agent[6556]: (/Stage[main]/Certregen::Client/File[/var/lib/puppet/ssl/certs/ca.pem]/content) content changed &apos;{md5}35d987adf79eaaed26780cd0e69f7d52&apos; to &apos;{md5}ed6cb1e53b7ad78ac41629fb8e3e2c38&apos;
Oct 17 04:06:59 host1 puppet-agent[6556]: (/Stage[main]/Certregen::Client/File[/var/lib/puppet/ssl/crl.pem]/content) content changed &apos;{md5}910a663110f54bf974a26b750c3a8a90&apos; to &apos;{md5}a774a54175b68251b11d0b2e264eec37&apos;</code></pre><p>If in doubt, you can check that the CA certificate serials of your Puppet CA and your client nodess match!</p><pre><code>root@puppetca # openssl x509 -in $(puppet config print cacert) -noout -text | grep Serial
        Serial Number: 7 (0x7)

root@node1 # openssl x509 -in $(puppet config print localcacert) -noout -text | grep Serial
        Serial Number: 7 (0x7)</code></pre><p>And whew, you&#x2019;ve just avoided what could have been a major problem. Just like that. Thanks, Puppet!</p><h2 id="things-to-remember-or-is-it-things-i-found-out-">Things to remember (or is it &#x201C;things I found&#xA0;out&#x201D;?)</h2><p>After including the&#xA0;::certregen::client class, the content of each node&#x2019;s$::localcacert and $::hostcrl files will be managed by Puppet, and kept up to date with the most recent versions located on your Puppetmasters.</p><p>That means that if a certificate is revoked on your CA, an entry will be added to its CRL. And that new version of the CRL will subsequently be pushed out across all of the nodes with the&#xA0;::certregen::client class included in their manifests.</p><p>More importantly, consider whether&#xA0;::certregen::client is going to affect your Puppet CA&#x2019;s <em>own catalog</em>. Consider where your Puppet CA gets <em>its</em> catalog from.</p><p>Is it itself? Then you&#x2019;re good.</p><p>Is it a different master or a load balancer containing a number of compile masters? If so, things could get messy.</p><p>If your Puppet CA hits another master for its catalog, it may receive an older version of the CRL than it has itself. In reality, this just means that $::cacert and $::localcacert will differ on your Puppet CA (as will $::cacrl and $::hostcrl), which may add some confusion if you start inspecting certs.</p><p>For me, I had a rouge $::cacert living on one of my compile masters, which really added some spice into the mix. It all worked out in the end, though.</p><p>To avoid any of these potential issues, I&#x2019;d make sure the value of server in /etc/puppet/puppet.conf for your Puppet CA is itself. Probably worth making sure your compile masters point at your Puppet CA for their catalogs, too.

</p>]]></content:encoded></item><item><title><![CDATA[Connecting the dots with scp]]></title><description><![CDATA[
Today, one of my colleagues was having trouble copying some files between Linux boxes. As a result, I learnt something about scp that I…
]]></description><link>https://blog.hybby.net/connecting-the-dots-with--scp-/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedbd</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Fri, 18 Nov 2016 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Today, one of my colleagues was having trouble copying some files between Linux boxes. As a result, I learnt something about scp that I didn&#x2019;t know before&#x200A;&#x2014;&#x200A;by default, using scp between two <em>remote</em> hosts from a third party host means that SSH connections &#x201C;leapfrog&#x201D; through the box where the source file is located.</p><p>Let me set the scene&#x200A;&#x2014;&#x200A;imagine three hosts:</p><ul><li>A host calledbastionthat scp copies are initiated from.</li><li>A host called sourcebox that has a file you want to copy (/tmp/source_file).</li><li>A host called destbox that has a place you want to copy the file to (/tmp/destfile).</li></ul><p>Imagine that our private key (~/.ssh/id_rsa) is physically present on bastion, but not on either sourcebox or destbox.</p><p>Our corresponding public key (~/.ssh/id_rsa.pub) is configured in the~/.ssh/authorized_keys file of all three hosts. We can therefore authenticate to each of these three boxes via ssh by using our private key.</p><p>Our /tmp/source_file exists on sourcebox and is accessible from bastion.</p><pre><code>drew@bastion:~$ ssh -i ~/.ssh/id_rsa sourcebox &apos;cat /tmp/source_file&apos;
content of my file</code></pre><p>Likewise, we can tell that there&#x2019;s no file called /tmp/dest_file on destbox.</p><pre><code>drew@bastion:~$ ssh -i ~/.ssh/id_rsa destbox &apos;ls -ld /tmp/dest_file&apos;
ls: cannot access /tmp/dest_file: No such file or directory</code></pre><p>Okay, let&#x2019;s try and copy our file by using scp from bastion:</p><pre><code>drew@bastion:~$ scp -i ~/.ssh/id_rsa sourcebox:/tmp/source_file sourcebox:/tmp/dest_file
Host key verification failed.
lost connection</code></pre><p>Huh? But&#x2026; ssh worked before between bastion and both of our other hosts... What gives? Both of these hosts are located in the ~/.ssh/known_hosts file of bastion, too&#x2026;</p><pre><code>drew@bastion:~$ ssh-keygen -F sourcebox | grep found
# Host sourcebox found: line 3 type ECDSA

drew@bastion:~$ ssh-keygen -F destbox | grep found
# Host destbox found: line 4 type ECDSA</code></pre><p>Let&#x2019;s dig a bit deeper with a bit of ssh&apos;s -vvv option&#x2026; (I&#x2019;ve omitted a lot of the noise with ellipsis to make it easier to read)</p><pre><code>drew@bastion:~$ scp -vvv -i ~/.ssh/id_rsa sourcebox:/tmp/source_file destbox:/tmp/dest_file
...
debug1: Host &apos;sourcebox&apos; is known and matches the ECDSA host key.
...
debug1: Authentication succeeded (publickey).
Authenticated to sourcebox ([192.168.10.12]:22).
...
debug1: Connecting to destbox [192.168.10.13] port 22.
...
debug1: Sending command: scp -v /tmp/source_file destbox:/tmp/dest_file
...
Host key verification failed.
lost connection</code></pre><p>So, it seems like when we invoke scp from bastion, we first connect to sourcebox and then initiate a connection <em>from there</em> to destbox.</p><p>As it stands, sourcebox doesn&#x2019;t know about destbox, so we can fix that by adding destbox&apos;s host keys&#x2026;</p><pre><code>drew@sourcebox:~$ ssh-keyscan destbox &gt;&gt; ~/.ssh/known_hosts
# destbox SSH-2.0-OpenSSH_6.6p1 Ubuntu-2ubuntu1
# destbox SSH-2.0-OpenSSH_6.6p1 Ubuntu-2ubuntu1

drew@sourcebox:~$ ssh-keygen -F destbox | grep found
# Host destbox found: line 1 type RSA
# Host destbox found: line 2 type ECDSA</code></pre><p>Let&#x2019;s try again and see where we stand&#x2026;</p><pre><code>drew@bastion:~$ scp -i ~/.ssh/id_rsa sourcebox:/tmp/source_file destbox:/tmp/dest_file
Permission denied, please try again.
Permission denied, please try again.
Permission denied (publickey,password).
lost connection</code></pre><p>Our host key problem is now resolved. Our sourcebox host now successfully validates the host key of destbox. But we have another problem&#x2026;</p><p>Now we&#x2019;re getting authentication failure messages. Basically, the private key that we&#x2019;re using from bastion isn&#x2019;t present on sourcebox, so we&#x2019;re effectively trying to authenticate to destbox from sourceboxwithout a password, an attempt which is swiftly rebuffed by destbox.</p><p>You can fix this from bastion by using a feature of ssh called AgentForwarding. First, you load your private key into ssh-agentand tell scpto forward it by using -o ForwardAgent=yes...</p><pre><code>drew@bastion:~$ eval $(ssh-agent)
Agent pid 3117

drew@bastion:~$ ssh-add
Identity added: /home/drew/.ssh/id_rsa (/home/drew/.ssh/id_rsa)

drew@bastion:~$ scp -o ForwardAgent=true sourcebox:/tmp/source_file destbox:/tmp/dest_file

drew@bastion:~$ ssh destbox &apos;cat /tmp/dest_file&apos;
content of my file</code></pre><p>Another way around this without using ssh-agent <em>or </em>having to configure anything on sourcebox would be to use the -3 option that comes with scp. From scp(1):</p><blockquote>-3 Copies between two remote hosts are transferred through the local host. Without this option the data is copied directly between the two remote hosts. Note that this option disables the progress meter.</blockquote><p>This would change the connections that are made by scp from this:</p><pre><code>bastion -&gt; sourcebox    # initiate connection to sourcebox
sourcebox -&gt; destbox    # copy from sourcebox to destbox</code></pre><p>to this:</p><pre><code>bastion -&gt; sourcebox    # copy from sourcebox to bastion
bastion -&gt; destbox      # copy from bastion to destbox</code></pre><p>Both of these are a bit more convenient than manually setting up SSH tunnels or anything like that. And both options help to reduce the number of places you have to plonk your private key, which is always good.</p><p>I&#x2019;ve been using these tools for a long time now, but for some reason in my head I always assumed that connections were made from <em>the place that the command was invoked</em>. TIL, I guess.

</p>]]></content:encoded></item><item><title><![CDATA[Deleting EBS Volumes While Snapshotting]]></title><description><![CDATA[
I’ve been working with AWS quite heavily recently. A lot of the time, I find myself thinking, “I wonder what happens if I do this…”
]]></description><link>https://blog.hybby.net/deleting-ebs-volumes-while-snapshotting/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedbf</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Tue, 26 Jul 2016 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>I&#x2019;ve been working with AWS quite heavily recently. A lot of the time, I find myself thinking, <em>&#x201C;I wonder what happens if I do this&#x2026;&#x201D;</em></p><p>Sometimes, I can find the answer in Amazon&#x2019;s fairly extensive documentation library. But sometimes, I want to perform events in a certain order where the consequences aren&#x2019;t described clearly.</p><p>For example, <em>&#x201C;What happens if I delete an EBS volume while a snapshot operation is taking place?&#x201D;</em>. Advice that I could find implied that snapshots were asynchronous operations and that volumes were safe to use after the snapshot was initiated (incurring a performance penalty, of course). However, nothing was said about deletions.</p><p>I tested it out. Turns out that if you delete a volume while a snapshot&#x2019;s being taken, the volume remains in a <em>Deleting</em> state until the snapshot&#x2019;s completed. Once the snapshot is finished, the volume is fully deleted.</p><p>This makes sense. I&#x2019;m glad it happens this way and we don&#x2019;t end up with failed snapshots or anything silly like that. +1 for sensibility, AWS.

</p>]]></content:encoded></item><item><title><![CDATA[Shimming a disk into a VxVM disk group]]></title><description><![CDATA[
We had a problem with an Oracle database on a four node Veritas Cluster Server (VCS) cluster recently. The symptom of our problem was that…
]]></description><link>https://blog.hybby.net/shimming-a-disk-into-a-vxvm-disk-group/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb3</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Fri, 25 Sep 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>We had a problem with an Oracle database on a four node <em>Veritas Cluster Server (VCS)</em> cluster<em> </em>recently. The symptom of our problem was that the database had been brought down with <em>VCS</em> and just <em>wouldn&#x2019;t</em> come back up again. The problem was ultimately traced down to the <em>Veritas Volume Manager (VxVM) </em>disk group level.</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/8859a632b1226f217adf.js"></script><!--kg-card-end: html--><h3 id="initial-analysis">Initial analysis</h3><p>The original diagnosis was that a SAN disk (LUN) had been accidentally unmapped from the host and put back hurriedly, resulting in it disappearing from the host. Volume migrations on our <em>IBM XIV</em> arrays have caused things like this in the past, and regular SCSI bus scans don&#x2019;t result in the LUN coming back in.</p><p>So, we thought we&#x2019;d take an outage, reboot and see if the LUN would come back in. It&#x2019;d been up for a couple of years and <em>did</em> have a lot of SAN storage presented to it (subject to relatively frequent change). Maybe the host&#x2019;s SCSI subsystem was being funky. We could patch while we were at it, too.</p><p>One reboot later and still no dice&#x200A;&#x2014;&#x200A;the missing LUN was nowhere to be found. I had a closer look at the disk group&#x2019;s configuration. Our missing LUN was only used in one of the <em>VxVM</em> volumes. It was a 256GB sub-disk tacked neatly onto the end of a healthy 1.4TB sub-disk.</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/0b0ccef50350f560461c.js"></script><!--kg-card-end: html--><p>There was only one data plex, so no mirroring in place. There&#x2019;d be no way to recover any data that was on this disk if the disk group was deemed to be borked.</p><p>So, to the immediate question: <em>&#x201C;Where is the disk?&#x201D;</em></p><p>If we could find it, reattaching it and recovering the volume would be a simple enough task. Through sheer luck, I found a text file containing a list of all LUNs configured visible from the host from a couple of months back. Searching for the DA name <strong>`xiv1_1234`</strong> threw up the LUN&#x2019;s human-friendly name (as configured on the array) along with its size and serial number.</p><p>Next stop was the array. I queried the array for a LUN of the same name. Nothing. Same size? Nothing. Serial number? Nothing.</p><p>There were only a few possibilities at this point:</p><ol><li>The LUN has been renamed and unmapped from the host.</li><li>The LUN has been deleted.</li></ol><p>At this stage, I turned to our storage guys. I gave them all of the details I had gathered. Turns out that the LUN had been deleted erroneously as part of a decommission of a completely different database <em>some months earlier</em>! And as it was a development database, there were no full backups to speak of.</p><p>However, I did think it was strange that our database hadn&#x2019;t noticed that one of its LUNs had disappeared! It had been running merrily for the past four months, oblivious. The cluster software had shut down it cleanly and deported its disks without a problem. It was only when trying to bring its storage back in again did we encounter our problem.</p><p>I speculated that since the database <em>had</em> been running fine before it was shut down, it must not have had any of its data located on the deleted LUN. If the database <em>had </em>tried to issue any read or write operations to the deleted LUN, there&#x2019;d have been I/O errors everywhere.</p><p><em>VxVM</em> usually writes sequentially to its volumes. Our deleted LUN had been right at the end of our impacted volume. A colleague&apos;s <strong>`df`</strong> output increased my confidence in this, although it was a <em>pretty close call!</em></p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/6b19d39e694608a92a0b.js"></script><!--kg-card-end: html--><p>Given all of this information, I wondered&#x200A;&#x2014;&#x200A;what if i grabbed a blank LUN of the same size and tried to fool <em>VxVM</em> into importing the disk group by using it as a shim? Worth a shot, right? So, I had the storage guys present a new LUN and take a snapshot of the surviving disk (in case i royally borked stuff up) and got to work.</p><hr><h3 id="you-ve-gotta-destroy-to-rebuild-">You&#x2019;ve gotta destroy to&#xA0;rebuild&#x2026;</h3><p>Because our volume had only one plex, it would be impossible for <em>VxVM</em> to rebuild any data that it thought was on our failed sub-disk. Therefore, standard disk recovery commands such as <strong>`vxrecover`</strong> wouldn&#x2019;t help us.</p><p>However, Symantec <a href="http://www.symantec.com/connect/downloads/script-change-group-id-veritas-disk-group">describe a method of destroying and recreating a disk group in place</a> for the purposes of changing its unique disk group ID (DGID). We could use this method to recreate our disk group, too! Of course, we&#x2019;d be making a few adjustments&#x2026;</p><p><em>VxVM</em> stores a lot of metadata to describe the layout of its disk groups and volumes. For example, the sub-disk configuration contains the path to the block device of the multipath device that it&#x2019;s stored on, which <em>Veritas Dynamic Multipather (VxDMP)</em> then distributes I/O to the storage paths. The metadata <em>completely </em>represents these data structures&#x200A;&#x2014;&#x200A;if you have all of the metadata, it&#x2019;s possible to recreate a disk group that&#x2019;s been destroyed, as long as nothing&#x2019;s damaged them.</p><p>Because I was able to partially import the disk group, I was able to dump the configuration of that disk group (we had one good LUN, remember?).</p><pre><code># vxprint -g datadb_DEV -hmvps &gt;&gt; /var/tmp/datadb_DEV.layout</code></pre><p>Even if I wasn&#x2019;t able to do this, there&#x2019;d be a good chance that <strong>`vxbackupd`</strong> would have stored some backup configuration copies from some point in <strong>`/etc/vx/cbr/bk`</strong>.</p><p>My troublesome volume was made up of two sub-disks. What a lucky situation&#x200A;&#x2014;&#x200A;since I had one <em>good</em> sub-disk and one <em>bad </em>sub-disk, I was able to compare the metadata information and figure out what I had to change to make my bad sub-disk look good again&#x2026;</p><figure class="kg-card kg-image-card"><img src="https://blog.hybby.net/content/images/downloaded_images/Shimming-a-disk-into-a-VxVM-disk-group/1--JoLknwjzimY_e8Ga2wAQA.png" class="kg-image" alt loading="lazy"></figure><p>Great! I know what I need to add, and I know what I need to change to make a good sub-disk. I just needed a few more pieces of information, first.</p><p>One thing that I didn&#x2019;t have available to me already was the <strong>`dev`</strong> string for my newly assigned shim disk. I also needed the unique <strong>`guid`</strong> for the disk, too. These were easy to find, but I needed to temporarily configure the disk to find it out:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/2f199a2cc32c7347013c.js"></script><!--kg-card-end: html--><p>I also needed what <em>VxVM</em> believed to be the correct associations between <em>Disk Access (DA)</em> or <em>Dynamic Multipath (DMP)</em> names&#x200A;&#x2014;&#x200A;the multipath block device&#x200A;&#x2014;&#x200A;and DM names&#x200A;&#x2014;&#x200A;a name assigned by humans to each disk.</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/c358da2f62a72a9f7728.js"></script><!--kg-card-end: html--><p>Finally, I needed the public and private region offsets for each LUN in the disk group. These are required so that when we reinitialise our disks, we can tell <em>VxVM</em> where its disk header (private region) is, and where the data starts (the public region). Without these exact values, we run the risk of overwriting areas that used to contain data with new headers, instantly corrupting the disk. For good.</p><p>These values were simple to get from the healthy LUN (<strong>`vxdisk list`</strong>). However, for the failed LUN, I had to go through a bunch of old configuration copy backups located in <strong>`/etc/vx/cbr/bk`</strong> and hope that I&#x2019;d find config for my failed disk. It was my lucky day, again.</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/caca2cf61600277239b4.js"></script><!--kg-card-end: html--><p>With my information in hand, I edited the following fields within my <strong>`/var/tmp/datadb_DEV.layout`</strong> file to match my newly assigned LUN. Here are the fields that I changed or added to my file:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/a2a899a43648eba7ce42.js"></script><!--kg-card-end: html--><p>I now had everything I needed to destroy and recreate my diskgroup.</p><h3 id="put-it-all-together-and-what-do-you-get">Put it all together and what do you&#xA0;get?</h3><ol><li>Ensure the disk group is deported</li><li>Reinitialise all disks with the correct public and private region offsets</li><li>Use our updated metadata to recreate our disk group</li><li>Activate our <em>VxVM</em> volumes and <strong>`fsck`</strong> them</li><li>Mount our filesystems</li><li>???</li><li>Profit!</li></ol><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/423e6db1ac88af82de90.js"></script><!--kg-card-end: html--><p>After this, the troublesome volume was marked as ENABLED. The filesystem checks returned okay. Everything mounted successfully. I asked a couple of our database guys to have a look and try to open the database instance.</p><p>The database opened! So, the shim worked! <em>VxVM</em> had no idea that the disk had been swapped out from under it!</p><p>I have to concede that in this situation, I was extremely lucky. What were the chances of an unused (but configured) disk being deleted from an array? And what were the chances of the database not trying to write to that disk for a number of months afterwards? Most amazingly, what were the chances of being able pulling off some brazen disk butchery without a hitch?</p><p>Time for a beer!

</p>]]></content:encoded></item><item><title><![CDATA[Using dlpiping to test VCS heartbeats]]></title><description><![CDATA[
Imagine you have a couple of hosts with Veritas Cluster Server (VCS) installed, configured with the recommended two heartbeat interfaces…
]]></description><link>https://blog.hybby.net/using-dlpiping-to-test-vcs-heartbeats/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb5</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Wed, 16 Sep 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>Imagine you have a couple of hosts with <em>Veritas Cluster Server (VCS)</em> installed, configured with the recommended two heartbeat interfaces. Only, you&#x2019;re not sure whether they&#x2019;re able to communicate or not!</p><p>In the normal TCP/IP world, you&#x2019;d just <strong>ping</strong> the hosts via the relevant network interfaces. However, <em>VCS</em> heartbeats run over the <em>Low Latency Transport (LLT)</em> protocol, which operates at layer 2 of the OSI model. Therefore, its interfaces don&#x2019;t require IP addresses to be configured on them. Enter <strong>dlpiping</strong>! A utility that allows us to test connectivity using the <em>LLT</em> protocol!</p><p>The <strong>VRTSllt</strong> package installs the <strong>dlipiping</strong> command in <strong>/opt/VRTSllt</strong>, so it requires that you&#x2019;ve gone through the VCS installation process.</p><p>First, decide the host that you want to test connectivity <em>to</em>. Identify one of your heartbeat interfaces. In my case, we&#x2019;re using <strong>eth5</strong>. Grab the hardware address of the interface. Let&#x2019;s imagine that&#x2019;s <strong>01:23:45:67:89:ab</strong> in our case.</p><p>Now, execute <strong>dlpiping</strong> in <em>server mode</em>.</p><pre><code>root@host1# /opt/VRTSllt/dlpiping -vs eth5
using packet size = 78
dlpiping: binding ping SAP 0xf00e</code></pre><p>Now, on the host you want to test connectivity <em>from</em>, send a request!</p><pre><code>root@host2# /opt/VRTSllt/dlpiping -vc eth5 01:23:45:67:89:ab
using packet size = 78
dlpiping: sent a request to 01:23:45:67:89:ab
dlpiping: received a packet from 01:23:45:67:89:ab
01:23:45:67:89:ab is alive</code></pre><p>Hooray! On your first host, you&#x2019;ll see a response pop up, too.</p><pre><code>dlpiping: sent a response</code></pre><p>This is an invaluable tool in troubleshooting those times when your cluster just won&#x2019;t come up, no matter what you do! This will give you reassurance that at least your network is working. Or alternatively, confirmation that you&#x2019;ve got lower level issues!

</p>]]></content:encoded></item><item><title><![CDATA[Capturing CDP Packets with tcpdump]]></title><description><![CDATA[
An oldie, but a goodie.
]]></description><link>https://blog.hybby.net/capturing-cdp-packets-with-tcpdump/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedb4</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Thu, 03 Sep 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>An oldie, but a goodie.</p><p>Using <strong>tcpdump</strong>, you can filter for the first CDP packet received by a given network interface. This can contain super-useful information for troubleshooting network issues.</p><pre><code>tcpdump -nn -v -i eth0 -s 1500 -c 1 &apos;ether[20:2] == 0x2000&apos;</code></pre><p>Big ones for me are <em>Native VLAN ID</em> and <em>Port-ID</em>, both invaluable when figuring out why you can&#x2019;t hit that elusive default gateway!

</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/6b9cc22e8a6287ddd3a9.js"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[When VxVM snapshots fail]]></title><description><![CDATA[
So, I was casually using the snapshot function of Vertias Volume Manager (VxVM) the other day and learned something new. Let me tell you…
]]></description><link>https://blog.hybby.net/when-vxvm-snapshots-fail/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedc1</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Wed, 12 Aug 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>So, I was casually using the snapshot function of <em>Vertias Volume Manager (VxVM)</em> the other day and learned something new. Let me tell you about it!</p><p>If you don&#x2019;t know about them, <em>VxVM snapshots</em> allow you to create a temporary mirror of a volume&#x2019;s primary data plex, the idea being that it&#x2019;s going to be split off into separate volume later on. This is useful when used in combination with <strong>vxdg split</strong>&#x200A;&#x2014;&#x200A;effectively allowing you to clone a disk group with very little effort (as long as you have the disk space!).</p><p>You need a Storage Foundation Enterprise license to use the <strong>vxdg split</strong> functionality. But in practice, the workflow looks like:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/954c6716a58b8518bb1f.js"></script><!--kg-card-end: html--><p>Now, you have a new disk group containing a copy of your source volume. The disk group has a new disk group ID too, so there&#x2019;s no fear of clashing. Both can reside happily on the same host, imported simultaneously.</p><p>However, I came across a gotcha when performing the snapshot operation:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/85ce9a1c099b5f9cf321.js"></script><!--kg-card-end: html--><p>No amount of searching Symantec&#x2019;s documentation could shed light on this. The error message was obscure and didn&#x2019;t seem to point me in any particular direction.</p><p>Thankfully, one of my colleagues (thanks, Susan W.!) pointed out the following <a href="https://support.symantec.com/en_US/article.TECH18445.html">little known piece of information</a> regarding <em>VxVM</em> volumes:</p><blockquote>Disk group and volume names can only contain up to 31 characters.</blockquote><p>Huh. Let&#x2019;s see&#x2026;</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/04bac40129cdd1090444.js"></script><!--kg-card-end: html--><p>Argh. My <strong>_new</strong> suffix had put me over the line! After changing my target volume name to something with fewer characters, everything worked as expected.

</p>]]></content:encoded></item><item><title><![CDATA[Error in vxdg configuration copies]]></title><description><![CDATA[
A colleague and I experienced a pretty non-trivial issue with Veritas Volume Manager (VxVM) today. The host was running Red Hat Enterprise…
]]></description><link>https://blog.hybby.net/error-in-vxdg-configuration-copies/</link><guid isPermaLink="false">5f20ce047e0d8f0001bfedbc</guid><dc:creator><![CDATA[drew]]></dc:creator><pubDate>Tue, 11 Aug 2015 00:00:00 GMT</pubDate><content:encoded><![CDATA[<p>A colleague and I experienced a pretty non-trivial issue with <em>Veritas Volume Manager (VxVM) </em>today. The host was running Red Hat Enterprise Linux 5.8 (an HP ProLiant DL380 G7, hooked up to some IBM XIV SAN storage via fibre channel).</p><p>It was a pretty old host and had been up for about 600 days. <strong>vxdisk -eo alldgs list</strong> was reporting that one of my standard volumes was actually a <em>snapshot!</em></p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/dccb389d3198904a9043.js"></script><!--kg-card-end: html--><p>What the heck? Had someone added a read/write snapshot to the diskgroup and extended onto it? Surely not!</p><p>I checked the array. Both disks were standard volumes. Hmm. And <em>whew</em>!</p><p>My next thought went to <em>VxVM</em>. The host had been up a while. Chances were that the storage configuration had changed quite frequently without sysadmins performing the correct disk removal / reassignment commands typically associated with that sort of work.</p><p>My next thought was, &#x201C;Let&#x2019;s restart <em>VxVM</em>! That&#x2019;s impactless, <em>right</em>?&#x201D;. Freezing my cluster service groups first, I performed the following:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/dfc726800fe4e1d46c1d.js"></script><!--kg-card-end: html--><p>Uhoh. That doesn&#x2019;t look too hot&#x200A;&#x2014;&#x200A;<strong>vxconfigd</strong> wouldn&#x2019;t come back. Not a lot of info as to why here. Let&#x2019;s check <strong>/var/log/messages</strong>&#x2026;</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/8ddfa28f0a7a30afe976.js"></script><!--kg-card-end: html--><p>Good ol&#x2019; <em>Veritas </em>data corruption protection. Somewhere along the line, one of the LUNs presented to this system had been unpresented and then represented with a new LUN ID, causing all kinds of havoc.</p><p>It&#x2019;s always a pain to resolve, but I&#x2019;m in a particular pickle here, as <em>VxVM </em>wants me to run <strong>vxdisk rm</strong> to remove the badly behaved multipath devices from its configuration. I can&#x2019;t issue this command until <strong>vxconfigd</strong> is running and in <em>enabled</em> mode, but I also can&#x2019;t get <strong>vxconfigd</strong> running until I do this! A true chicken and egg scenario&#x2026;</p><p>Looking more closely at the log output, only three block devices were causing the problems: <strong>sdcy</strong>, <strong>sdcn</strong> and <strong>sdy</strong>. What disk group do they belong to?</p><p>a quick <strong>egrep </strong>of my disk group backups in <strong>/etc/vx/cbr/bk</strong> (caution with these; they <em>can</em> be out of date depending on how often administrative disk operations take place and how <strong>vxconfigbackupd</strong> is configured) showed that they were members of the disk group that had been causing me problems: the one with the standard volume and apparent &#x2018;snapshot&#x2019;.</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/32d470c204cc9547efef.js"></script><!--kg-card-end: html--><p>I knew for a fact that:</p><ol><li>No I/O was passing through these disks (No filesystems were mounted)</li><li>Disks in this disk group had six redundant paths to storage each. This meant that each SAN volume had six block devices that the OS could use to route I/O requests.</li><li>I only wanted to get rid of three block devices.</li></ol><p>Therefore, I was <em>relatively</em> happy performing the following:</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/d4870e89f682d27d8756.js"></script><!--kg-card-end: html--><p>After that, I gingerly started<em> VxVM</em> again and tried to run <strong>vxdisk -eo alldgs list</strong> again&#x2026;</p><!--kg-card-begin: html--><script src="https://gist.github.com/hybby/8ed3e0d57bb637bca0af.js"></script><!--kg-card-end: html--><p>It&#x2019;s back! And <em>VxVM</em> agrees that our &#x2018;snapshot&#x2019; is now a standard volume! If we compared the output of <strong>vxdisk print xiv0_001</strong> from before and after our action, we&#x2019;d probably see that the names of the block devices that represented our storage devices had changed slightly, too.</p><p>Clearly something had been done at the storage layer with this particular disk. Exactly <em>what</em> will probably always remain a mystery. But if nothing else, this proves that performing sanity reboots before starting a migration is a pretty good idea.

</p>]]></content:encoded></item></channel></rss>