<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/chrome/</id>
  <link href="https://www.endpointdev.com/blog/tags/chrome/"/>
  <link href="https://www.endpointdev.com/blog/tags/chrome/" rel="self"/>
  <updated>2022-01-02T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Generate PDF with Chrome, Puppeteer, and Serverless Stack</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/"/>
      <id>https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/</id>
      <published>2022-01-02T00:00:00+00:00</published>
      <author>
        <name>Afif Sohaili</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/banner.jpg&#34; alt=&#34;Cloud Computing and Serverless&#34;&gt;&lt;/p&gt;
&lt;!-- Image from Pixabay, Pixabay license: https://pixabay.com/illustrations/cloud-network-website-computer-6515064/ --&gt;
&lt;p&gt;Function as a Service (FaaS) solutions are becoming more and more mainstream today. The FaaS model allows developers to not have to worry about managing infrastructure and instead focus on writing the application logic. In the FaaS model, developers write individual functions that run specific tasks that are deployed together on a FaaS platform, including but not limited to Amazon Web Services (AWS), Azure, and Google Cloud Platform (GCP).&lt;/p&gt;
&lt;p&gt;These functions don&amp;rsquo;t always run like a traditional application server does, waiting for a request. Instead, these FaaS platforms only spin up instances of these functions whenever there is traffic and will shut them back down once there are no more requests after a given period of time. This helps make FaaS a really cheap platform while traffic is low. It is a good approach for on-demand tasks that are part of the application but not necessarily the most common path in a customer&amp;rsquo;s everyday journey within the application.&lt;/p&gt;
&lt;h3 id=&#34;the-tools&#34;&gt;The tools&lt;/h3&gt;
&lt;p&gt;In this tutorial, we&amp;rsquo;re going to be looking at implementing PDF download with Chrome, Puppeteer, and Serverless Stack, but first, let&amp;rsquo;s have a brief introduction of the tools that we are going to be using.&lt;/p&gt;
&lt;h4 id=&#34;1-serverless-stack&#34;&gt;1. Serverless Stack&lt;/h4&gt;
&lt;p&gt;Serverless Stack is a framework for building full-stack serverless apps. The bigger player in this space is the Serverless framework. Serverless Stack is giving the Serverless framework (yes, I know, it can be very confusing at times) a challenge to the throne. The latter has been around for many years now and has been the authority for building serverless apps. There are pros and cons to both of them.&lt;/p&gt;
&lt;p&gt;Serverless Stack&amp;rsquo;s biggest advantage is the live Lambda debugging, a faster development process and overall better development experience. With the Serverless framework, developers have to either constantly deploy to the cloud to test any change, or spend some time setting up &lt;code&gt;serverless-offline&lt;/code&gt; for simulated Lambda and API gateway environments on their local machines.&lt;/p&gt;
&lt;p&gt;But with the Serverless framework, developers are not tied to using a specific platform, as it can be used to deploy functions on AWS, Azure, and Google Cloud Platform. Serverless Stack, on the other hand, only allows deploying to AWS since its internals are built on top of AWS CDK.&lt;/p&gt;
&lt;h4 id=&#34;2-chrome-and-puppeteer&#34;&gt;2. Chrome and Puppeteer&lt;/h4&gt;
&lt;p&gt;Chrome will be used in this tutorial as a headless browser, controlled using Puppeteer. Puppeteer is a Node.js package that provides an API to make Chrome or Chromium navigate to a specific URL, take screenshots, click buttons, type in input fields, as well as take PDF screenshots. Note that the PDF screenshot feature is specific to Chrome and not Chromium, so make sure to double check that you are using the right browser.&lt;/p&gt;
&lt;h3 id=&#34;implementing-pdf-screenshot-and-download-with-serverless-stack-chrome-and-puppeteer&#34;&gt;Implementing PDF screenshot and download with Serverless Stack, Chrome, and Puppeteer&lt;/h3&gt;
&lt;p&gt;Now, let&amp;rsquo;s get to the meat of this tutorial. We are going to implement PDF generation with Chrome and Puppeteer, deployed on AWS Lambda through Serverless Stack. Here is what we will do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer in Chrome to navigate to a receipt page that we implemented.&lt;/li&gt;
&lt;li&gt;Instruct Chrome to take a PDF screenshot of the page.&lt;/li&gt;
&lt;li&gt;Stream the PDF file back to the user.&lt;/li&gt;
&lt;li&gt;Deploy this implementation on AWS Lambda using Serverless Stack and demonstrate the development experience throughout.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PDF generation is a great use case for FaaS. It represents something that doesn&amp;rsquo;t happen very often and is triggered on demand by the user. Instead of letting our monolith application handle that intermittent traffic, we can delegate the task to FaaS in the cloud which will do this job without worrying us about scaling the infrastructure to cater to this particular use case. Then our application can just focus on handling the regular web traffic for general use.&lt;/p&gt;
&lt;h4 id=&#34;prerequisite-sample-receipt-pages&#34;&gt;Prerequisite: Sample receipt pages&lt;/h4&gt;
&lt;p&gt;First, let&amp;rsquo;s create some demo pages for PDF screenshots. I&amp;rsquo;m not going to go in-depth on these since it&amp;rsquo;s not the topic of the day. I have deployed these simple HTML pages that are styled like receipts on this Surge instance &lt;a href=&#34;https://unwieldy-key.surge.sh/&#34;&gt;here&lt;/a&gt;. You can find the code on GitHub &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;getting-started&#34;&gt;Getting started&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s generate a new Serverless Stack project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npx create-serverless-stack@latest pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations, we have just created a new project with Serverless Stack! Here we get a basic template of a working Serverless Stack app. You can go &lt;a href=&#34;https://docs.serverless-stack.com/installation&#34;&gt;here&lt;/a&gt; for more information on how to get started with Serverless Stack.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s look at the files we get from bootstrapping the project and customize them wherever we see fit.&lt;/p&gt;
&lt;h5 id=&#34;1-sstjson&#34;&gt;1. sst.json&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;pdf-generator&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;region&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;stacks/index.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This file is the entry point to the Serverless Stack app, and here we can define the region of choice, the name of the app, and the main file that Serverless Stack will use.&lt;/p&gt;
&lt;h5 id=&#34;2-stacksindexjs&#34;&gt;2. stacks/index.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; app.setDefaultFunctionProps({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;stacks/index.js&lt;/code&gt; is the main file declared in &lt;code&gt;sst.json&lt;/code&gt;. When building the project, Serverless Stack will use this file as an entry point to our application. This file is pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It configures the Lambda functions to use the Node.js 12.x runtime.&lt;/li&gt;
&lt;li&gt;It registers a stack called &lt;code&gt;my-stack&lt;/code&gt;. This is the name of the CloudFormation stack on AWS. We definitely want to give it a better name than &lt;code&gt;my-stack&lt;/code&gt; here, so let&amp;rsquo;s rename that to &lt;code&gt;pdf-generator&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;3-stacksmystackjs&#34;&gt;3. stacks/MyStack.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; * as sst from &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@serverless-stack/resources&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; MyStack &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;extends&lt;/span&gt; sst.Stack {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  constructor(scope, id, props) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;super&lt;/span&gt;(scope, id, props);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; api = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; sst.Api(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Api&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;GET /&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;src/lambda.handler&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Show the endpoint in the output
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.addOutputs({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;ApiEndpoint&amp;#34;&lt;/span&gt;: api.url,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;MyStack.js&lt;/code&gt; is where we declare the resources we need inside a given CloudFormation stack. This can be anything from a cluster of Lambda functions, API Gateway endpoints, DynamoDB tables, S3 buckets, etc. The full list of resources that we can create with Serverless Stack is listed &lt;a href=&#34;https://docs.serverless-stack.com/constructs/Api&#34;&gt;here in the documentation&lt;/a&gt;. For our project we just need one API endpoint with API gateway, so what we are provided with here is already sufficient.&lt;/p&gt;
&lt;h4 id=&#34;deploying-the-project&#34;&gt;Deploying the project&lt;/h4&gt;
&lt;p&gt;Now, we can try to deploy this project to AWS. Ensure that the AWS Access Key ID and AWS Secret Access Key are set in the development environment, and then run &lt;code&gt;npx sst start&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# export AWS credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;=&amp;lt;access key id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;=&amp;lt;secret access key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# or if the credentials are set in ~/.aws/credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_PROFILE&lt;/span&gt;=&amp;lt;AWS profile name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Start the app in development mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npx sst start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a few minutes, we will have the Cloudformation stack deployed on our AWS account. Keep in mind that the first deployment will take a bit longer than subsequent deployments. When the deployment is done, we should be able to see that it is watching for file changes from our local machine.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::ApiGatewayV2::Route | ApiRouteGET8AC7D3F8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::Lambda::Permission | ApiRouteGETthreadlightlypdfgeneratorpdfgeneratorApiRouteGET7230D6CDPermissionE4542537
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::CloudFormation::Stack | threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ✅  threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Stack threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Status: deployed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Outputs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ApiEndpoint: https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Starting Live Lambda Dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Transpiling Lambda code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Debug session started. Listening for requests...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s head to the API URL given in the output and we should get a response back like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! Your request was received at 30/Nov/2021:06:21:13 +0000.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;developing-with-serverless-stack&#34;&gt;Developing with Serverless Stack&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s try updating one of the files provided to see how we can test the changes we have made and will make.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    &amp;#34;GET /&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we did two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We updated the API endpoint to &lt;code&gt;GET /downloads/receipt&lt;/code&gt; instead of just &lt;code&gt;GET /&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We made the Lambda function return the URL given in the query string.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we save these changes we should see Serverless Stack automatically reloading our code and since we changed the API endpoint path, Serverless Stack will need to make changes to our infrastructure (i.e. the API Gateway resources), and whenever it detects that it has to change the infrastructure of our application, it will immediately prompt us to redeploy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding infrastructure...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Done building code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Press ENTER to redeploy infrastructure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once we hit ENTER, Serverless Stack will automatically update our infrastructure for us: delete our old endpoint, create a new endpoint on &lt;code&gt;GET /downloads/receipt&lt;/code&gt;, and hook up our &lt;code&gt;src/lambda.js&lt;/code&gt; file to handle that endpoint.&lt;/p&gt;
&lt;p&gt;When it&amp;rsquo;s ready, try hitting the API gateway URL again, but this time append &lt;code&gt;/downloads/receipt?url=https://google.com&lt;/code&gt; to it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://google.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello! You&amp;#39;ve requested to print the receipt at page https://google.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great, now we have learned how to make changes to our Serverless Stack application during development. Let&amp;rsquo;s go ahead and add Puppeteer and Chrome to our Lambda function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; import * as sst from &amp;#34;@serverless-stack/resources&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import { LayerVersion } from &amp;#34;@aws-cdk/aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const layerArn = &amp;#34;arn:aws:lambda:us-east-1:764866452798:layer:chrome-aws-lambda:25&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-     &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     &amp;#34;GET /downloads/receipt&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       function: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         handler: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Increase the timeout for generating screenshots
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         timeout: 15,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Load Chrome in a Layer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         layers: [LayerVersion.fromLayerVersionArn(this, &amp;#34;Layer&amp;#34;, layerArn)],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Exclude bundling it in the Lambda function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         bundle: { externalModules: [&amp;#34;chrome-aws-lambda&amp;#34;] },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, we are configuring our AWS Lambda function to use a Lambda layer that includes Chrome in our Lambda functions. You can think of these Lambda layers as being similar to NPM packages that you pull for extending your projects except that this is a 3rd party package for extending AWS Lambda functions instead.&lt;/p&gt;
&lt;p&gt;With this layer, your AWS Lambda function will boot with the Chrome binary already included. This layer is maintained &lt;a href=&#34;https://github.com/shelfio/chrome-aws-lambda-layer&#34;&gt;here&lt;/a&gt;. Make sure you&amp;rsquo;re using the ARN from the right region. In this tutorial, we are using &lt;code&gt;us-east-1&lt;/code&gt; per Serverless Stack&amp;rsquo;s default, so we are going to pick the ARN for &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s update our Lambda handler to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer to instruct Chrome to navigate to the URL passed in the parameter.&lt;/li&gt;
&lt;li&gt;Take a screenshot of the web page and save a PDF out of that. Puppeteer provides a handy &lt;code&gt;page.pdf&lt;/code&gt; API to do just that.&lt;/li&gt;
&lt;li&gt;Ultimately, stream that PDF file back to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s the code for that, with some comments to help you out:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import chrome from &amp;#34;chrome-aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const puppeteer = chrome.puppeteer;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; export async function handler(event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  return {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    headers: { &amp;#34;Content-Type&amp;#34;: &amp;#34;text/plain&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    body: `Hello! You&amp;#39;ve requested to print the receipt at page ${event.queryStringParameters.url}`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let browser
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  try {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const { url } = event.queryStringParameters;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    browser = await puppeteer.launch({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      args: chrome.args,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      executablePath: await chrome.executablePath,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const page = await browser.newPage();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Use A5 size at 150dpi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const width = 874
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const height = 1240
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.setViewport({ width, height });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Navigate to the url
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.goto(url, { waitUntil: &amp;#39;networkidle2&amp;#39; });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } catch(err) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 500,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: `An error occured. ${err.message}`})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } finally {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await browser &amp;amp;&amp;amp; browser.close()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  return response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s also not forget to install Puppeteer and Chrome.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install puppeteer puppeteer-core chrome-aws-lambda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, give Serverless Stack some time to reload our new changes. Once we have the API endpoint ready to go, let&amp;rsquo;s head to the browser and hit our API endpoint, passing the URL to the sample receipt page that we did earlier as a query param.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Visit https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://unwieldy-key.surge.sh/index.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Response:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&amp;#34;message&amp;#34;:&amp;#34;Screenshot taken&amp;#34;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great! We are getting back a 200 status code and a message saying &amp;ldquo;Screenshot taken&amp;rdquo;. That means our code works and there were no exceptions. We&amp;rsquo;re halfway there.&lt;/p&gt;
&lt;p&gt;Now, if we check the project folder, we should now see a new file called &lt;code&gt;receipt.pdf&lt;/code&gt; there. This is the &lt;code&gt;receipt.pdf&lt;/code&gt; captured by Puppeteer. We can open this file and verify that this is the demo receipt page that I deployed earlier that we passed to the endpoint.&lt;/p&gt;
&lt;p&gt;We can also hit the API endpoint with the second sample receipt URL (i.e. &lt;code&gt;url=https://unwieldy-key.surge.sh/receipt-2.html&lt;/code&gt;) and we should see the &lt;code&gt;receipt.pdf&lt;/code&gt; file gets replaced with the receipt from the new URL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/page-screenshot.png&#34; alt=&#34;receipt.pdf file that we get&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that in a real-world application, the URL you would want to screenshot might need some form of authentication, so you are going to have to figure that out for your application. In this case the sample receipt page is accessible publicly, so we don&amp;rsquo;t run into this problem, but chances are your users&amp;rsquo; receipt page will not be accessible publicly like this.&lt;/p&gt;
&lt;p&gt;One way you could solve this is to create a special user account that has permission to visit these authenticated pages for this purpose. Then, prior to visiting the receipt page, you&amp;rsquo;d program Puppeteer to first log in as the special user and then head to the receipt page URL to be screenshot.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;However, we are only halfway there. We can see the &lt;code&gt;receipt.pdf&lt;/code&gt; file now only because the &lt;code&gt;receipt.pdf&lt;/code&gt; file was made available to us by Serverless Stack, which automatically downloads that file to our local machine in development mode. In a real AWS Lambda execution, the file will remain in the filesystem of that Lambda execution context instead, and the user will not see it.&lt;/p&gt;
&lt;p&gt;Hence we have to read and stream that file back to the user instead of returning a JSON message saying &amp;ldquo;Screenshot taken&amp;rdquo;. Getting a 200 status code is a great start, but that still does not give our users the actual PDF that they wanted, so let&amp;rsquo;s update our Lambda handler to do just that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // src/lambda.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import * as fs from &amp;#34;fs&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const buffer = fs.readFileSync(&amp;#39;receipt.pdf&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; response = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  headers: {&amp;#39;Content-type&amp;#39; : &amp;#39;application/pdf&amp;#39;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  body: buffer.toString(&amp;#39;base64&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  isBase64Encoded : true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The changes this time are pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import the &lt;code&gt;fs&lt;/code&gt; module from Node.js, which will give us access to &lt;code&gt;readFileSync&lt;/code&gt; API that can read files on the disk.&lt;/li&gt;
&lt;li&gt;Read the generated &lt;code&gt;receipt.pdf&lt;/code&gt; file as a Buffer.&lt;/li&gt;
&lt;li&gt;Return the file&amp;rsquo;s Buffer as a base64 string in the response body.&lt;/li&gt;
&lt;li&gt;Set the content-type to be &lt;code&gt;application/pdf&lt;/code&gt;. Setting the content-type appropriately will tell the browser what to do with the file. Some browsers will display the PDF in an in-browser PDF reader, while others will download the file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it! Now let&amp;rsquo;s test the change. Hit the same URL again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/pdf-file.png&#34; alt=&#34;The PDF returned in a response, rendered in a PDF viewer in Edge&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bingo, the endpoint now returns the PDF to the user! Now we can integrate this endpoint with our main application to delegate these on-demand PDF generation to managed FaaS infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;FaaS platforms are a really great way to offload intermittent and on-demand traffic from our main application. This ensures our application runs smoother and can serve our general customers better, while at the same time handling these on-demand tasks really well.&lt;/p&gt;
&lt;h5 id=&#34;caveats&#34;&gt;Caveats&lt;/h5&gt;
&lt;p&gt;Building a full-blown application on the stack is also possible, but in my personal experience, it comes with its own complexities and tradeoffs that may or may not be worth the engineering time.&lt;/p&gt;
&lt;p&gt;For example, databases are an issue with going full-blown development on FaaS. Because FaaS can scale indefinitely, the number of connections to the database is a limitation.&lt;/p&gt;
&lt;p&gt;You can switch to FaaS-friendly databases such as DynamoDB or FaunaDB, but these are NoSQL databases and require a different mindset in structuring the application data than the simpler relational databases and that can cost tremendous engineering time. Relational databases are easier to work with for most types of applications.&lt;/p&gt;
&lt;p&gt;Nonetheless, more and more tools are being developed to tackle these complexities, such as &lt;a href=&#34;https://planetscale.com/&#34;&gt;PlanetScale&lt;/a&gt; and &lt;a href=&#34;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-2.how-it-works.html&#34;&gt;Serverless Aurora v2&lt;/a&gt; (still in preview). All in all, it is definitely a trend worth following closely.&lt;/p&gt;
&lt;h3 id=&#34;further-readings&#34;&gt;Further readings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Full code on GitHub: &lt;a href=&#34;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&#34;&gt;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code for the sample receipt pages: &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;https://github.com/afifsohaili-ep/receipt-demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless Stack: &lt;a href=&#34;https://serverless-stack.com/&#34;&gt;https://serverless-stack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless framework: &lt;a href=&#34;https://serverless.com/&#34;&gt;https://serverless.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.alldaydevops.com/blog/serverless-databases-the-good-the-bad-and-the-ugly&#34;&gt;Serverless Databases: The Good, the Bad, and the Ugly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Symantec Certificate Distrust (CertQuake)</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/12/symantec-certificate-distrust-certquake/"/>
      <id>https://www.endpointdev.com/blog/2017/12/symantec-certificate-distrust-certquake/</id>
      <published>2017-12-15T00:00:00+00:00</published>
      <author>
        <name>Josh Lavin</name>
      </author>
      <content type="html">
        &lt;p&gt;If you are accustomed to running your browser with the “developer tools” panel open (which probably indicates you are a web developer), you may have seen it show the following message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The certificate used to load &lt;a href=&#34;https://www.example.com/&#34;&gt;https://www.example.com/&lt;/a&gt; uses an SSL
certificate that will be distrusted in an upcoming release of Chrome.
Once distrusted, users will be prevented from loading this resource.
See &lt;a href=&#34;https://g.co/chrome/symantecpkicerts&#34;&gt;https://g.co/chrome/symantecpkicerts&lt;/a&gt; for more information.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;What’s this all about? Glad you asked.&lt;/p&gt;
&lt;h3 id=&#34;the-root-of-all-certificates-well-most&#34;&gt;The Root of All Certificates (well, most)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symantec&lt;/strong&gt; is a company that operated a “PKI” (&lt;a href=&#34;https://en.wikipedia.org/wiki/Public_key_infrastructure&#34;&gt;Public Key Infrastructure&lt;/a&gt;) business. As a &lt;a href=&#34;https://en.wikipedia.org/wiki/Certificate_authority&#34;&gt;Certificate Authority&lt;/a&gt;, they would dole out digital certificates to requestors.&lt;/p&gt;
&lt;h4 id=&#34;certified&#34;&gt;Certified&lt;/h4&gt;
&lt;p&gt;These certificates are used to secure the communication we have with websites. When a site uses a certificate correctly, you will see the leading part of the URL begin with &lt;code&gt;https://&lt;/code&gt; (known as the protocol), and the “green-lock” icon in your browser.&lt;/p&gt;
&lt;p&gt;Certificates can also be issued with more stringent requirements on the company obtaining them, where they must verify their company by providing articles of incorporation, etc. These are known as “EV” (Extended Validation) certs, and browsers will show the company name in the URL bar next to the green-lock icon.&lt;/p&gt;
&lt;h4 id=&#34;essential-trust&#34;&gt;Essential Trust&lt;/h4&gt;
&lt;p&gt;Since users have become accustomed to trusting a website if the green-lock icon is present, especially if the name of the company behind the website appears in the URL bar alongside it, there is a lot of inherent trust in the system. &lt;strong&gt;All parties must honor that trust by operating with adherence to well-established security requirements.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The system to provide this trust includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;browsers&lt;/em&gt;, who trust a built-in set of “root” certificates&lt;/li&gt;
&lt;li&gt;&lt;em&gt;root certificate issuers&lt;/em&gt;, who can send trust down the line to certificates ordered from their infrastructure (like Symantec)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;websites&lt;/em&gt;, who install their certificates and configure their applications and web servers to use them properly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;poor-decisions&#34;&gt;Poor Decisions&lt;/h3&gt;
&lt;p&gt;At several points over time, Symantec made decisions that Google and others felt &lt;strong&gt;jeopardized the system’s inherent trust&lt;/strong&gt;. You can &lt;a href=&#34;https://wiki.mozilla.org/CA:Symantec_Issues&#34;&gt;read more on those&lt;/a&gt;, but the result is that Google decided that enough is enough: they would &lt;strong&gt;remove trust in Symantec’s roots from the Chrome browser&lt;/strong&gt;. Mozilla followed soon after for their Firefox browser.&lt;/p&gt;
&lt;p&gt;The first distrust will occur in &lt;strong&gt;March 2018&lt;/strong&gt;, when Chrome 66 is released to beta.&lt;/p&gt;
&lt;p&gt;Symantec also resold their security offerings to multiple partners. Thus, several certificate vendors with completely different names turned out to use Symantec root certificates behind the scenes, and are also affected. This includes &lt;em&gt;all&lt;/em&gt; certificates with Symantec as the root issuer, such as &lt;strong&gt;Equifax, GeoTrust, Thawte, and VeriSign&lt;/strong&gt;, among others.&lt;/p&gt;
&lt;p&gt;You can read more on &lt;a href=&#34;https://security.googleblog.com/2017/09/chromes-plan-to-distrust-symantec.html&#34;&gt;Google’s plan&lt;/a&gt;, and also see &lt;a href=&#34;https://blog.qualys.com/ssllabs/2017/09/26/google-and-mozilla-deprecating-existing-symantec-certificates&#34;&gt;Qualys’s overview on the situation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;quaking-in-our-boots&#34;&gt;Quaking In Our Boots&lt;/h3&gt;
&lt;p&gt;This root-level distrust is a big deal, so in the spirit of &lt;a href=&#34;https://medium.com/threat-intel/bug-branding-heartbleed-14ef1a64047f&#34;&gt;catchy names for security vulnerabilities&lt;/a&gt;, End Point is referring to this as &lt;strong&gt;CertQuake&lt;/strong&gt;. Maybe it will catch on. Alas, probably not, because we have more important things to do than design fancy artwork for it.&lt;/p&gt;
&lt;h3 id=&#34;how-to-tell&#34;&gt;How To Tell&lt;/h3&gt;
&lt;p&gt;The easiest way to tell if your site is affected is to use Qualys’s &lt;a href=&#34;https://www.ssllabs.com/ssltest/index.html&#34;&gt;SSL Server Test&lt;/a&gt;. Just enter your hostname in the box, and let Qualys scan your site.&lt;/p&gt;
&lt;p&gt;If you are affected by the Symantec distrust, there will be a large yellow warning box, near the top of the page.&lt;/p&gt;
&lt;h3 id=&#34;where-to-go-from-here&#34;&gt;Where To Go From Here&lt;/h3&gt;
&lt;p&gt;If you operate a site that is affected, you need to &lt;strong&gt;act quickly to re-issue&lt;/strong&gt; your certificate from your certificate vendor. While the deadline can vary based on when your certificate was issued, we are using the March 2018 date for all certificates we manage.&lt;/p&gt;
&lt;p&gt;Some issuers are offering &lt;a href=&#34;https://www.namecheap.com/symantec-replace/&#34;&gt;free replacement&lt;/a&gt; of affected certs with their own certificates, to try to capture market share from Symantec and its survivor, DigiCert.&lt;/p&gt;
&lt;h3 id=&#34;what-we-did&#34;&gt;What We Did&lt;/h3&gt;
&lt;p&gt;Since End Point manages hundreds of secure websites for our clients, it would be time-consuming to manually check each website to see if it is affected.&lt;/p&gt;
&lt;p&gt;Instead, we downloaded a copy of all affected root certificates, which we &lt;a href=&#34;https://chromium.googlesource.com/chromium/src/+/master/net/data/ssl/symantec/roots/&#34;&gt;found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then, armed with a list of all our secure client websites, we devised a set of Bash scripts that would extract the issuers from the root certs and compare them to our clients’ certificate issuers.&lt;/p&gt;
&lt;p&gt;We then were able to take a list of affected certs, and unless they were going to expire before March 2018, re-issue them.&lt;/p&gt;
&lt;p&gt;If you are interested in our Bash scripts, we have placed a &lt;a href=&#34;https://github.com/jdigory/symantec-distrust&#34;&gt;copy on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;summary&#34;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Our process allowed us to quickly discover all affected certificates, and re-issue them for our clients. Most of the time, this was done behind the scenes, where our clients didn’t even need to get involved—​we were on the job, protecting them and their businesses.&lt;/p&gt;
&lt;p&gt;In this Internet Age, we all have to do our part to ensure trust in the system. It’s unfortunate when one thing can affect so many websites, but it’s how the system works.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>One-time password SSH solutions</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/02/one-time-password-ssh-solutions/"/>
      <id>https://www.endpointdev.com/blog/2015/02/one-time-password-ssh-solutions/</id>
      <published>2015-02-02T00:00:00+00:00</published>
      <author>
        <name>Greg Sabino Mullane</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; float:right; text-align: center; margin-bottom: 1.5em; margin-left: 3em; margin-right: 2em; &#34;&gt;&lt;a href=&#34;/blog/2015/02/one-time-password-ssh-solutions/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/02/one-time-password-ssh-solutions/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;small&gt;[how encryption was done in the 18th century]&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;In a &lt;a href=&#34;/blog/2015/01/ssh-one-time-passwords-otpw-on/&#34;&gt;previous article&lt;/a&gt; I explained how I used a one‑time password system to enable SSH on my Chromebook. While this is still working great for me, there are a few problems that can pop
up.&lt;/p&gt;
&lt;h3 id=&#34;problem-1-tripping-the-alarm&#34;&gt;Problem 1: Tripping the alarm&lt;/h3&gt;
&lt;p&gt;Normally a single password is asked for when logging in using one-time passwords via the &lt;a href=&#34;https://www.cl.cam.ac.uk/~mgk25/otpw.html&#34;&gt;otpw program&lt;/a&gt;. However, otpw will issue a three‑password prompt when it thinks someone may be trying to perform a race attack (in other words, it think two people are trying to log in at once). The prompt will change from the normal one to this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Password 206/002/011:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This alarm can be tripped from SSH timing out, or from getting pulled away from your computer mid‑login. In theory, it could be someone trying to break in to my laptop, but that is very unlikely. Needless to say, trying to squint at a paper and keyboard in the dark is hard enough with one password, much less three! It’s usually much easier (in most cases) for me to walk to my laptop and remove the &lt;strong&gt;~/.otpw.lock&lt;/strong&gt; file, which will clear the “intruder alarm” and cause otpw to prompt for a single password again. Another solution is to set a timeout on clearing the lock, for example via a cronjob that removes the lock file if it was created more than 10 minutes ago. Finally, it may be helpful to have a way to remotely clear the lock. I’ve not written it yet, but one good approach would be to use &lt;a href=&#34;http://portknocking.org/&#34;&gt;port knocking&lt;/a&gt; to remove the lock file.&lt;/p&gt;
&lt;h3 id=&#34;problem-2-lost-or-compromised-passwords&#34;&gt;Problem 2: Lost or compromised passwords&lt;/h3&gt;
&lt;p&gt;The physical sheet of paper containing your one‑time passwords is definitely a single point of failure—​but an easy one to remedy. Maybe you lost the paper, maybe your arch‑nemesis stole it, or maybe it got destroyed in a freak hunting accident. No worries at all, just generate a new one! Run the otpw‑bin command again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ otpw-gen -e 30 | lpr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When you do so, the old &lt;strong&gt;~/.otpw&lt;/strong&gt; file is completely overwritten, and the old sheet of paper is now completely worthless. Solutions don’t get any easier than that. If your sheet is stolen and you need to quickly disable one‑time passwords, you can also just manually remove the .otpw file, which effectively turns off one‑time passwords for that account.&lt;/p&gt;
&lt;h3 id=&#34;problem-3-unusable-passwords&#34;&gt;Problem 3: Unusable passwords&lt;/h3&gt;
&lt;p&gt;The password to use for login is randomly determined, so one time you may be asked to look up password 312 and the next 031. Sometimes, however, you cannot use one of the passwords on your printout. Perhaps you spilled something on it. Perhaps (as has happened to me!) the paper was folded in such a way that the crease made it difficult to read the characters. Perhaps your keyboard has a really hard time typing a certain letter. :) Whatever the reason, there are two solutions. First, generate a new sheet by rerunning otpw‑bin as described in Problem 2 above. Second, you can mark the current password as “complete” and have otpw move on to the next number.&lt;/p&gt;
&lt;p&gt;Forcing otpw to advance to the next number is slightly tricky. Basically, you need to modify the .otpw file and change the current password hash to a line of hyphens. Here is what the top of a .otpw file looks like after 3 successful logins:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OTPW1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;392 3 12 5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;077jA:EAgMCJ2uM
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;097yG3IDv%gyUB7
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1077T7EQq%K7E/F
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;101xeS3I+zMw8GZ
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;109xCEBXYFb3%3v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;121AzwjOJYyBqD%
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;068WewLA3EIsLmx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;065Jdq=2WDwHZ9D
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;089npYNavK9MIVA&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first line states the format of the file, while the second line indicates the number of passwords generated, the digits per password number, the digits in the hash, and the digits in the actual passwords. All the other lines are passwords—​either an unused one consisting of the number and the hash, or a line of hyphens. The goal is to replace the current password with hyphens. Here’s a quick recipe to do so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;perl -ni -e &amp;#39;print unless /^\d\d\d\S/ and ! $x++&amp;#39; ~/.otpw&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We do an in‑place edit (&lt;strong&gt;-i&lt;/strong&gt;) of the .otpw file, looping through a line at a time (&lt;strong&gt;-n&lt;/strong&gt;), and run a command against each line (&lt;strong&gt;-e &amp;hellip;&lt;/strong&gt;). The first time we find a line that starts with three numbers and then contains non‑whitespace, we skip it. Everything else is printed and thus goes back into the file.&lt;/p&gt;
&lt;p&gt;Those are some ways to handle three of the common problems that can occur when using one‑time passwords. Have other problems, or better solutions to the above? Let me know in the comments.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>SSH one-time passwords (otpw) on chromebook</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/01/ssh-one-time-passwords-otpw-on/"/>
      <id>https://www.endpointdev.com/blog/2015/01/ssh-one-time-passwords-otpw-on/</id>
      <published>2015-01-21T00:00:00+00:00</published>
      <author>
        <name>Greg Sabino Mullane</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; float: right; margin-bottom: 1em; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/ssh-one-time-passwords-otpw-on/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/01/ssh-one-time-passwords-otpw-on/image-0.jpeg&#34;/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;small&gt;&lt;a href=&#34;https://flic.kr/p/e55Nqb&#34;&gt;Henri Coandă Bucureşt Airport&lt;/a&gt;&lt;br/&gt;by &lt;a href=&#34;https://www.flickr.com/photos/bortescristian/&#34;&gt;Cristian Bortes&lt;/a&gt;&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;A little while ago, I bought a &lt;a href=&#34;https://www.samsung.com/us/computer/chromebook&#34;&gt;Chromebook&lt;/a&gt; as an alternative to my sturdy-but-heavy laptop. So far, it has been great—​quick boot up, no fan, long battery life, and light as a feather. Perfect for bringing from room to room, and for getting some work done in a darkened bedroom at night. The one large drawback was a lack of &lt;a href=&#34;https://en.wikipedia.org/wiki/Secure_Shell&#34;&gt;SSH&lt;/a&gt;, a tool I use very often. I’ll describe how I used one-time passwords to overcome this problem, and made my Chromebook a much more productive tool.&lt;/p&gt;
&lt;p&gt;The options for using SSH on &lt;a href=&#34;https://en.wikipedia.org/wiki/Chrome_OS&#34;&gt;Chrome OS&lt;/a&gt; are not that good. I downloaded and tried a handful of apps, but each had some significant problems. One flaw shared across all of them was a lack of something like &lt;a href=&#34;https://en.wikipedia.org/wiki/Ssh-agent&#34;&gt;ssh-agent&lt;/a&gt;, which will cache your SSH passphrase so that you don’t have to type it every time you open a new SSH session. An option was to use a password-less key, or a very short passphrase, but I did not want to make everything less secure. The storage of the SSH private key was an issue as well—​the Chromebook has very limited storage options, and relies on putting most things “in the cloud”.&lt;/p&gt;
&lt;p&gt;What was needed was a way to use SSH in a very insecure environment, while providing as much security as possible. Eureka! A &lt;a href=&#34;https://en.wikipedia.org/wiki/One-time_password&#34;&gt;one-time password&lt;/a&gt; system is exactly what I needed. Specifically, the wonderful &lt;a href=&#34;http://www.cl.cam.ac.uk/~mgk25/otpw.html&#34;&gt;otpw program&lt;/a&gt;. Chromebooks have a simple shell (accessed via ctrl-alt-t) that has SSH support. So the solution was to use one-time passwords and not store anything at all on the Chromebook.&lt;/p&gt;
&lt;p&gt;Rather than trying to get otpw setup on all the servers I might need to reach, I simply set it up on my main laptop, carefully allowed incoming SSH connections, and now I can ssh from my Chromebook to my laptop. From there, to the world. Best of all, when I ssh in, I can use the already running ssh-agent on the laptop! All it takes is memorizing a single passphrase and securing a sheet of paper (which is far easier to secure than an entire Chromebook :)&lt;/p&gt;
&lt;p&gt;Here are some details on how I set things up. On the Chromebook, nothing is needed except to open up a crosh tab with ctrl-alt-t, and run ssh. On the laptop side, the first step is to install the otpw program, and then configure PAM so that it uses it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo aptitude install otpw-bin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo cat &amp;gt;&amp;gt; /etc/pam.d/ssh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  auth     required  pam_otpw.so
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  session  optional  pam_otpw.so&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is the bare minimum, but I also wanted to make sure that only ‘local’ machines could SSH in. While there are a number of ways to do this, such as iptables or /etc/hosts.allow, I decided the best approach was to configure sshd itself. The “Match” directive instructs that the lines after it only take effect on a positive match. Thus:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo cat &amp;gt;&amp;gt; /etc/ssh/sshd_config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AllowUsers nobodyatall
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Match Address 192.168.1.0/24,127.0.0.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AllowUsers greg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ service ssh restart&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next step is to create the one-time password list. This is done with the otwp-gen program; here is the command I use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ otpw-gen -e 30 | lpr
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating random seed ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;If your paper password list is stolen, the thief should not gain access to your account with this information alone. Therefore, you need to memorize and enter below a prefix password. You will have to enter that each time directly before entering the one-time password (on the same line).
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;When you log in, a 3-digit password number will be displayed. It identifies the one-time password on your list that you have to append to the prefix password. If another login to your account is in progress at the same time, several password numbers may be shown and all corresponding passwords have to be appended after the prefix password. Best generate a new password list when you have used up half of the old one.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Enter new prefix password: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Reenter prefix password: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Creating &amp;#39;~/.otpw&amp;#39;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating new one-time passwords ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The otpw-gen command creates a file named &lt;strong&gt;.otpw&lt;/strong&gt; in your home directory, which contains the hash of all the one-time passwords to use. In the example above, the &lt;strong&gt;-e&lt;/strong&gt; controls the entropy of the generated passwords—​in other words, how long they are. otpw-gen will not accept an entropy lower than 30, which will generate passwords that are five characters long. The default entropy, 48, generates passwords that are eight characters long, which I found a little too long to remember when trying to read from the printout in a dark room. :). Rather than show the list of passwords on the screen, or save them to a local file, the output goes directly to the printer. otpw-gen does a great job of formatting the page, and it ends up looking like this:&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2015/01/ssh-one-time-passwords-otpw-on/image-1-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2015/01/ssh-one-time-passwords-otpw-on/image-1.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Here are some close-ups of what the passwords look like at various entropies:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Sample output with a low entropy of 30:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OTPW list generated 2015-07-12 13:23 on gregsbox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;000 GGS%F  056 bTqut  112 f8iJs  168 lQVjk  224 gNG2x  280 -x8ke  336 egm5n
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;001 urHLf  057 a/Wwh  113 -PEpV  169 9ABpK  225 -K2db  281 babfX  337 feeED
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;002 vqrX:  058 rZszx  114 r3m8a  170 -UzX3  226 g74RI  282 gusBJ  338 ;Tr4m
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;003 fa%6G  059 -i4FZ  115 nPEaJ  171 o64FR  227 uBu:h  283 uBo/U  339 ;pYY8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;004 -LYZY  060 vWDnw  116 f5Sb+  172 hopr+  228 rWXvb  284 rksPQ  340 ;v6GN&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Sample output with the default entropy of 48:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OTPW list generated 2015-15-05 15:53 on gregsbox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;000 tcsx qqlb  056 ougp yuzo  112 lxwt oitl  168 giap vqsj  224 vtvk rjc/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;001 mfui ukph  057 wbpw aktt  113 kert wozj  169 ihed psyx  225 ducx pze=
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;002 wwsj hdcr  058 jmwa mguo  114 idtk zrzw  170 ecow fepm  226 ikru hty+
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;003 aoeb klnz  059 pvie fbfc  115 fmlb sptb  171 ftrd jotb  227 mqns ivq:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;004 yclw hyml  060 slvj ezfi  116 djsy ycse  172 butg guzm  228 pfyv ytq%
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;005 eilj cufp  061 zlma yxxl  117 skyf ieht  173 vbtd rmsy  229 pzyn zlc/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Sample output with a high entropy of 79:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OTPW list generated 2015-07-05 18:74 on gregsbox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;000 jeo SqM bQ9Y ato  056 AyT jsc YbU0 rXB  112 Og/ I3O 39nY W/Z
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;001 AFk W+5 J+2m e1J  057 MXy O9j FjA8 8q;  113 a6A 8R9 /Ofr E4s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;002 02+ XPB 8B2S +qT  058 Cl4 6g2 /9Bk KO=  114 HEK vd3 T2TT Rr.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;003 Exb jqE iK49 rfX  059 Qhz eU+ J2VG kwQ  115 aJ7 tg1 dJsr vf.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;004 Bg1 b;5 p0qI f/m  060 VKz dpa G7;e 7jR  116 kaL OSw dC8e kx.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The final step is to SSH from the Chromebook to the laptop! Hit ctrl-alt-t, and you will get a new tab with a crosh prompt. From there, attempt to ssh to the laptop, and you will see the usual otpw prompt:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh greg@192.168.1.10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Password 140: &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So you type in the passphrase you entered above when running the otpw-gen command, then pull out your sheet of paper and look up the matching password next to number 140. Voila! I am now connected securely to my more powerful computer, and can SSH from there to anywhere I am used to going to from my laptop. I can even run mutt as if I were at the laptop! A nice workaround for the limitations of the Chromebook.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>CSS Conf US 2014 — Part Two</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/06/css-conf-us-2014-part-two/"/>
      <id>https://www.endpointdev.com/blog/2014/06/css-conf-us-2014-part-two/</id>
      <published>2014-06-04T00:00:00+00:00</published>
      <author>
        <name>Greg Davidson</name>
      </author>
      <content type="html">
        &lt;h3 id=&#34;more-thoughts-on-getting-vertical-testing-and-icon-fonts&#34;&gt;More Thoughts on Getting Vertical, Testing and Icon Fonts&lt;/h3&gt;
&lt;p&gt;Without further ado I’ve written up another batch of my notes about three more great talks at CSS Conf US in Amelia Island, Florida last week.&lt;/p&gt;
&lt;h3 id=&#34;antoine-butler--embrace-the-vertical&#34;&gt;Antoine Butler — Embrace the Vertical&lt;/h3&gt;
&lt;p&gt;Antoine shared his observation that vertical media queries are available to CSS developers but not
often used. With the &lt;a href=&#34;https://opensignal.com/reports/fragmentation-2013/&#34;&gt;vast array&lt;/a&gt;
of devices accessing the web today vertical media queries can be a useful tool to adapt your content effectively. Antoine walked us through a couple examples of how he applied this technique in a couple of his projects. The first was a prototype of WikiPedia. While they have gone with a separated mobile site (e.g. en.m.wikipedia.org/), he started with the HTML from the desktop site and applied some vertical media queries to make the content much more digestible. Take a look at &lt;a href=&#34;https://codepen.io/aebsr/pen/BapraL&#34;&gt;his code&lt;/a&gt; to see how it works.&lt;/p&gt;
&lt;p&gt;The second example Antoine demonstrated was for the navigation at &lt;a href=&#34;http://www.vw.com/&#34;&gt;Volkswagen&lt;/a&gt;. The client wanted to display an unlimited number of items in the secondary navigation. Once again Antoine applied vertical media queries to handle the varying number of navigation elements based on the device height. Check out his &lt;a href=&#34;https://codepen.io/aebsr/pen/MWpjoM&#34;&gt;adaptive sticky vertical navigation code&lt;/a&gt; for a closer look.&lt;/p&gt;
&lt;p&gt;Slides from this talk are available here: &lt;a href=&#34;https://speakerdeck.com/aebsr/embrace-the-vertical&#34;&gt;Embrace the Vertical&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;christophe-burgmer--if-your-css-is-happy-and-you-know-it&#34;&gt;Christophe Burgmer — If your CSS is happy and you know it&amp;hellip;&lt;/h3&gt;
&lt;p&gt;This was a really interesting talk about testing your CSS visually with a tool Christophe has been developing called &lt;a href=&#34;http://cburgmer.github.io/csscritic/&#34;&gt;CSS Critic&lt;/a&gt;. Christophe covered some of the existing CSS/HTML testing tools like &lt;a href=&#34;http://docs.seleniumhq.org/&#34;&gt;Selenium&lt;/a&gt; and found that while they worked well they didn’t meet his needs entirely. He wanted a way to visually diff the changes that were made and to be able to write tests for his UI code. For example, when the “accepted” version of the page changed visually, he wanted to be notified and decide whether or not to accept the proposed change.&lt;/p&gt;
&lt;p&gt;Christophe demoed the tool for us and it was really cool to see a visual diff in the browser. For a change that was introduced, screenshots of the old, new and difference were displayed. The user then has the ability to accept / OK the change or reject it. You can view the tool in action on the &lt;a href=&#34;http://cburgmer.github.io/csscritic/&#34;&gt;CSS Critic&lt;/a&gt; site. Under the hood, CSS Critic uses some other nifty projects including &lt;a href=&#34;https://github.com/BBC-News/wraith&#34;&gt;Wraith&lt;/a&gt;, &lt;a href=&#34;https://github.com/Huddle/PhantomCSS&#34;&gt;PhantomCSS&lt;/a&gt;, &lt;a href=&#34;http://casperjs.org/&#34;&gt;CasperJS&lt;/a&gt; and &lt;a href=&#34;https://web.archive.org/web/20160728015647/http://hardy.io/&#34;&gt;Hardy&lt;/a&gt;. Christophe also mentioned &lt;a href=&#34;http://csste.st/&#34;&gt;csste.st&lt;/a&gt; as a site which curates information on all of these topics and projects.&lt;/p&gt;
&lt;p&gt;Slides from this talk are available here: &lt;a href=&#34;http://cburgmer.github.io/csscritic/cssconf2014/#/step-1&#34;&gt;If you CSS is happy and you know it&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;zach-leatherman--bulletproof-icon-fonts&#34;&gt;Zach Leatherman — Bulletproof Icon Fonts&lt;/h3&gt;
&lt;p&gt;Zach wrote a &lt;a href=&#34;https://filamentgroup.com/lab/bulletproof_icon_fonts.html&#34;&gt;great article&lt;/a&gt; on Bulletproof Accessible Icon Fonts earlier this year and his talk was along similar lines. He chronicled some of the challenges and pitfalls worth knowing about in order to support icon fonts in your sites and applications. Browser support varies a great deal and Zach cited John Holt Ripley’s &lt;a href=&#34;https://web.archive.org/web/20161125011236/http://unicode.johnholtripley.co.uk:80/all/&#34;&gt;Unify&lt;/a&gt; unicode support charts as a helpful reference. He works on the &lt;a href=&#34;https://github.com/filamentgroup/a-font-garde&#34;&gt;a-font-garde&lt;/a&gt; project which documents best (er. bulletproof) practices for working with icon fonts today.&lt;/p&gt;
&lt;h3 id=&#34;stay-tuned&#34;&gt;Stay Tuned&lt;/h3&gt;
&lt;p&gt;Watch for one more post later this week with the last batch of talks from the conf!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Chrome, onmousemove, and MediaWiki JavaScript</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/04/chrome-onmousemove-and-mediawiki/"/>
      <id>https://www.endpointdev.com/blog/2014/04/chrome-onmousemove-and-mediawiki/</id>
      <published>2014-04-18T00:00:00+00:00</published>
      <author>
        <name>Greg Sabino Mullane</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; float: right; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/04/chrome-onmousemove-and-mediawiki/image-0-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/04/chrome-onmousemove-and-mediawiki/image-0.jpeg&#34;/&gt;&lt;/a&gt;
&lt;br/&gt;&lt;small&gt;&lt;a href=&#34;https://flic.kr/p/aM4L46&#34;&gt;Image&lt;/a&gt; by Flickr user &lt;a href=&#34;https://www.flickr.com/photos/archer10/&#34;&gt;Dennis Jarvis&lt;/a&gt;&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;tl;dr: avoid using onmousemove events with Google Chrome.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I recently fielded a complaint about not being able to select text with the mouse on a wiki running the &lt;a href=&#34;https://www.mediawiki.org/wiki/MediaWiki&#34;&gt; MediaWiki software&lt;/a&gt;. After some troubleshooting and research, I narrowed the problem down to a bug in the Chrome browser regarding the onmousemove event. The solution in this case was to tweak JavaScript to use onmouseover instead of onmousemove.&lt;/p&gt;
&lt;p&gt;The first step in troubleshooting is to duplicate the problem. In this case, the page worked fine for me in Firefox, so I tried using the same browser as the reporter: Chrome. Sure enough, I could no longer hold down the mouse button and select text on the page. Now that the browser was implicated, it was time to see what it was about this page that caused the problem.&lt;/p&gt;
&lt;p&gt;It seemed fairly unlikely that something like this would go unfixed if it was happening on the flagship MediaWiki site, Wikipedia. Sure enough, that site worked fine, I could select the text with no problem. Testing some other random sites showed no problems either. Some googling indicated others had similar problems with Chrome, and gave a bunch of workarounds for selecting the text. However, I wanted a fix, not a workaround.&lt;/p&gt;
&lt;p&gt;There were hints that JavaScript was involved, so I disabled JavaScript in Chrome, reloaded the page, and suddenly everything started working again. Call that big clue number two. The next step was to see what was different between the local MediaWiki installation and Wikipedia. The local site was a few versions behind, but I was fortuitously testing an upgrade on a test server. This showed the problem still existed on the newer version, which meant that the problem was something specific to the wiki itself.&lt;/p&gt;
&lt;p&gt;The most likely culprit was one of the many installed &lt;a href=&#34;https://www.mediawiki.org/wiki/Manual:Extensions&#34;&gt;MediaWiki extensions&lt;/a&gt;, which are small pieces of code that perform certain actions on a wiki. These often have their own JavaScript that they run, which was still the most likely problem.&lt;/p&gt;
&lt;p&gt;Then it was some basic troubleshooting. After turning JavaScript back on, I edited the LocalSettings.php file and commented out all the user-supplied extensions. This made the problem disappear again. Then I commented out half the extensions, then half again, etc., until I was able to narrow the problem down to a single extension.&lt;/p&gt;
&lt;p&gt;The extension in question, known simply as “&lt;a href=&#34;https://www.mediawiki.org/wiki/Extension:Balloons&#34;&gt;balloons&lt;/a&gt;”, has actually been removed from the MediaWiki extensions site, for “prolonged security issues with the code.” The extension allows creation of very nice looking pop up CSS “balloons” full of text. I’m guessing the concern is because the arguments for the balloons were not sanitized properly. In a public wiki, this would be a problem, but this was for a private intranet, so we were not worried about continuing to use the extension. As a side note, such changes would be caught anyway as this wiki sends an email to a few people on any change, including a full text diff of all the changes.&lt;/p&gt;
&lt;p&gt;Looking inside the JavaScript used by the extension, I was able to narrow the problem down to a single line inside balloons/js/balloons.js:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#888&#34;&gt;// Track the cursor every time the mouse moves
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#038&#34;&gt;document&lt;/span&gt;.onmousemove = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.setActiveCoordinates;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sure enough, duck-duck-going through the Internet quickly found &lt;a href=&#34;https://code.google.com/p/chromium/issues/detail?id=170631&#34;&gt;a fairly incriminating Chromium bug&lt;/a&gt;, indicating that onmousemove did not work very well at all. Looking over the balloon extension code, it appeared that onmouseover would probably be good enough to gather the same information and allow the extension to work while not blocking the ability for people to select text. One small replacement of “move” to “over”, and everything was back to working as it should!&lt;/p&gt;
&lt;p&gt;So in summary, if you cannot select text with the mouse in Chrome (or you see any other odd mouse-related behaviors), suspect an onmousemove issue.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Crossed siting; or How to Debug iOS Flash issues with Chrome</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/02/crossed-siting-or-how-to-debug-ios/"/>
      <id>https://www.endpointdev.com/blog/2013/02/crossed-siting-or-how-to-debug-ios/</id>
      <published>2013-02-19T00:00:00+00:00</published>
      <author>
        <name>Jeff Boes</name>
      </author>
      <content type="html">
        &lt;p&gt;This situation had all the elements of a programming war story: unfamiliar code, an absent author, a failure that only happens in production, and a platform inaccessible to the person in charge of fixing this: namely, me.&lt;/p&gt;
&lt;p&gt;Some time ago, an engineer wrote some Javascript code to replace a Flash element on a page with an HTML5 snippet, for browsers that don’t support Flash (looking at you, iOS). For various reasons, said code didn’t make it to production. Fast forward many months, and that engineer has left for another position, so I’m asked to test it, and get it into production.&lt;/p&gt;
&lt;p&gt;Of course, it works fine. My only test platform is an iPod, but it looks great here. Roll it out, and ker-thunk: it doesn’t work. Of course, debugging Javascript on an iPod is less than optimal, so I enlisted others with Apple devices and found that it mostly failed, but maybe worked a few times, depending on [SOMETHING].&lt;/p&gt;
&lt;p&gt;To make matters a bit worse, the Apache configurations for the test and production environments differed, just enough to raise my suspicions and convince me that was worth investigating. Once I went down that path, it was tough to jar myself loose from that suspicion.&lt;/p&gt;
&lt;p&gt;I tried disabling Flash in Firefox to trigger the substitution, but that didn’t seem to have the desired effect (as the replacement didn’t happen, which was a different error than the replacement &lt;em&gt;failing&lt;/em&gt;). I tried a browser emulation site (which shall remain nameless for this post, as I don’t think they are bad at what they do, but they don’t emulate iOS browsers in &lt;em&gt;this&lt;/em&gt; capacity).&lt;/p&gt;
&lt;p&gt;Eventually we disabled flash in Chrome (by visiting the chrome://plugins page). That unveiled the hidden error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;XMLHttpRequest cannot load http://www.somewhere.com/ajax/newstuff.html. Origin http://somewhere.com is not allowed by Access-Control-Allow-Origin.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There’s the crux of it: the browser was sitting on an address which appeared different than that of the AJAX target.&lt;/p&gt;
&lt;p&gt;The site involved is an Interchange site, and page constructed a URL using the [area] tag, which makes a fully-qualified URL from a fragment like “ajax/newstuff”. That URL was being seen by the iOS browsers as a cross-site scripting attempt, as it didn’t &lt;em&gt;precisely&lt;/em&gt; match where the browser found the page. The error was not visible in the Safari browser on my iPod, and browsers I had access to which &lt;em&gt;could&lt;/em&gt; have displayed the error, weren’t suffering it.&lt;/p&gt;
&lt;p&gt;I replaced the [area] tag with a plain relative URL and the problem disappeared.&lt;/p&gt;
&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;               $.ajax({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-                  url: &amp;#34;[area href=|ajax/newstuff|]&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;+                  url: &amp;#34;ajax/newstuff.html&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                   type: &amp;#39;html&amp;#39;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The original code caused a cross-site scripting failure.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
