<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>nunogrl.com - devops</title><link href="https://nunogrl.com/" rel="alternate"></link><link href="https://nunogrl.com/categories/devops/atom.xml" rel="self"></link><id>https://nunogrl.com/</id><updated>2024-03-21T00:00:00+00:00</updated><entry><title>Automating Server Startups with CloudFormation &amp; Sceptre</title><link href="https://nunogrl.com/articles/automating-server-startups-cloudformation-sceptre/" rel="alternate"></link><published>2024-03-21T00:00:00+00:00</published><updated>2024-03-21T00:00:00+00:00</updated><author><name>Nuno Leitao</name></author><id>tag:nunogrl.com,2024-03-21:/articles/automating-server-startups-cloudformation-sceptre/</id><summary type="html">&lt;p class="first last"&gt;Learn how to automate EC2 instance scheduling using CloudFormation and Sceptre, with CloudWatch Events and Lambda for reliable execution&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="problem-solution"&gt;
&lt;h2&gt;🚀 Problem &amp;amp; Solution&lt;/h2&gt;
&lt;div class="section" id="context-backstory"&gt;
&lt;h3&gt;📌 &lt;strong&gt;Context / Backstory&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We had a requirement to &lt;strong&gt;start a test EC2 instance automatically at 08:00&lt;/strong&gt; each morning. This server, called &lt;cite&gt;Tests01&lt;/cite&gt;, was only needed during working hours, and we wanted to avoid manual intervention or running it 24/7.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h3&gt;⚠️ &lt;strong&gt;The Problem&lt;/strong&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Manual starts were unreliable and easy to forget.&lt;/li&gt;
&lt;li&gt;CloudFormation alone doesn't handle timed triggers.&lt;/li&gt;
&lt;li&gt;Lambda + CloudWatch Events provide scheduling, but managing them manually is messy.&lt;/li&gt;
&lt;li&gt;We needed this &lt;strong&gt;fully automated and reproducible&lt;/strong&gt;, ideally within infrastructure-as-code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h3&gt;💡 &lt;strong&gt;The Solution&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We used &lt;strong&gt;Sceptre&lt;/strong&gt; to deploy a &lt;strong&gt;CloudFormation template&lt;/strong&gt; that provisions:
- A &lt;strong&gt;CloudWatch rule&lt;/strong&gt; triggered at 08:00
- A &lt;strong&gt;Lambda function&lt;/strong&gt; that starts the &lt;cite&gt;Tests01&lt;/cite&gt; instance
- All IAM roles and permissions necessary&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="who-this-helps"&gt;
&lt;h3&gt;👥 &lt;strong&gt;Who This Helps&lt;/strong&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;DevOps engineers using AWS automation&lt;/li&gt;
&lt;li&gt;Teams migrating from cron jobs to infrastructure-as-code&lt;/li&gt;
&lt;li&gt;Anyone needing &lt;strong&gt;scheduled resource management&lt;/strong&gt; in the cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="technical-implementation"&gt;
&lt;h2&gt;⚙️ Technical Implementation&lt;/h2&gt;
&lt;p&gt;Let's visualize the automation flow:&lt;/p&gt;
&lt;div class="mermaid" id="mermaid-diagram-6439199693712232369"&gt;
flowchart LR
   CW[CloudWatch Event]
   L[Lambda Function]
   EC2[EC2 Instance]

   CW --&gt;|"Trigger (08:00)"| L
   L --&gt;|"StartInstances API"| EC2

   subgraph "AWS Infrastructure"
   CW
   L
   EC2
   end&lt;/div&gt;&lt;div class="section" id="sceptre-project-setup"&gt;
&lt;h3&gt;1️⃣ Sceptre Project Setup&lt;/h3&gt;
&lt;p&gt;Project layout:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;config/
  config.yaml
  dev/
    start-tests.yaml
templates/
  start-tests.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Global &lt;cite&gt;config/config.yaml&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;project_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;startup-project&lt;/span&gt;
&lt;span class="nt"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;eu-west-1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Stack config &lt;cite&gt;config/dev/start-tests.yaml&lt;/cite&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;start-tests.yaml&lt;/span&gt;
&lt;span class="nt"&gt;stack_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;start-tests-scheduler&lt;/span&gt;
&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;InstanceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;i-0123456789abcdef0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="cloudformation-template"&gt;
&lt;h3&gt;2️⃣ CloudFormation Template&lt;/h3&gt;
&lt;p&gt;File: &lt;cite&gt;templates/start-tests.yaml&lt;/cite&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;2010-09-09&amp;#39;&lt;/span&gt;
&lt;span class="nt"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;InstanceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;String&lt;/span&gt;
&lt;span class="nt"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;LambdaExecutionRole&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS::IAM::Role&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;2012-10-17&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Allow&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nt"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lambda.amazonaws.com&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sts:AssumeRole&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Policies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PolicyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StartInstance&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;PolicyDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;2012-10-17&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Allow&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ec2:StartInstances&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;StartInstanceLambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS::Lambda::Function&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;index.handler&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!GetAtt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;LambdaExecutionRole.Arn&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python3.9&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;30&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;ZipFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;import boto3&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;import os&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;def handler(event, context):&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;ec2 = boto3.client(&amp;#39;ec2&amp;#39;)&lt;/span&gt;
&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;ec2.start_instances(InstanceIds=[os.environ[&amp;#39;INSTANCE_ID&amp;#39;]])&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;InstanceId&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;StartEventRule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS::Events::Rule&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;cron(0 8 * * ? *)&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Arn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!GetAtt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StartInstanceLambda.Arn&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;TargetFunctionV1&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;PermissionForEventsToInvokeLambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS::Lambda::Permission&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StartInstanceLambda&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;lambda:InvokeFunction&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;events.amazonaws.com&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;SourceArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!GetAtt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;StartEventRule.Arn&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting-debugging"&gt;
&lt;h2&gt;🛠️ Troubleshooting &amp;amp; Debugging&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Ensure the Lambda role has permissions to &lt;cite&gt;ec2:StartInstances&lt;/cite&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;cite&gt;aws lambda invoke&lt;/cite&gt; to manually test the function.&lt;/li&gt;
&lt;li&gt;Logs appear in CloudWatch Logs; confirm the instance ID is correct.&lt;/li&gt;
&lt;li&gt;Validate your cron expression with AWS docs: &lt;cite&gt;cron(0 8 * * ? *)&lt;/cite&gt; runs at 08:00 UTC.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="optimizations-alternatives"&gt;
&lt;h2&gt;🔁 Optimizations &amp;amp; Alternatives&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;You can also add a second Lambda + CloudWatch rule to &lt;strong&gt;stop&lt;/strong&gt; the instance at 18:00.&lt;/li&gt;
&lt;li&gt;Consider replacing this with &lt;strong&gt;AWS Systems Manager Automation documents&lt;/strong&gt; if you're not using CloudFormation.&lt;/li&gt;
&lt;li&gt;If you're managing multiple instances, parameterize the instance list or use tags.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion-takeaways"&gt;
&lt;h2&gt;✅ Conclusion &amp;amp; Takeaways&lt;/h2&gt;
&lt;p&gt;This setup gives you a &lt;strong&gt;reproducible, versioned, and automated method&lt;/strong&gt; for managing instance scheduling using CloudFormation and Sceptre. It's a clean way to handle what would otherwise be a manual or error-prone task—and it fits directly into a GitOps-style workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comments-next-steps"&gt;
&lt;h2&gt;💬 Comments &amp;amp; Next Steps&lt;/h2&gt;
&lt;p&gt;Have you implemented similar automation patterns with CloudFormation? Share your experience or ask questions below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Cloud"></category><category term="aws"></category><category term="cloudformation"></category><category term="sceptre"></category><category term="automation"></category><category term="lambda"></category><category term="cloudwatch"></category><category term="iac"></category><category term="devops"></category></entry><entry><title>Change Sets with Sceptre: Controlled AWS Changes in ITIL Environments</title><link href="https://nunogrl.com/articles/change-sets-sceptre-itil-environments/" rel="alternate"></link><published>2024-03-21T00:00:00+00:00</published><updated>2024-03-21T00:00:00+00:00</updated><author><name>Nuno Leitao</name></author><id>tag:nunogrl.com,2024-03-21:/articles/change-sets-sceptre-itil-environments/</id><summary type="html">&lt;p class="first last"&gt;Learn how to implement controlled AWS infrastructure changes in ITIL-governed environments using Sceptre and CloudFormation Change Sets&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="problem-solution"&gt;
&lt;h2&gt;🚀 Problem &amp;amp; Solution&lt;/h2&gt;
&lt;p&gt;📌 &lt;strong&gt;Context / Backstory&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We needed to &lt;strong&gt;add a stop-instance Lambda&lt;/strong&gt; to our existing AWS stack — but in a production environment governed by &lt;strong&gt;ITIL change control&lt;/strong&gt;. That meant:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;No untracked infrastructure changes&lt;/li&gt;
&lt;li&gt;No direct &amp;quot;deploy and hope&amp;quot; workflows&lt;/li&gt;
&lt;li&gt;Every change needed visibility, approval, and a rollback path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ &lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CloudFormation makes changes declarative — but deployments are immediate by default. In an ITIL environment, we needed:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A &lt;strong&gt;way to preview the change&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;record of the proposed change&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;controlled execution&lt;/strong&gt;, ideally during a change window&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 &lt;strong&gt;The Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We used &lt;strong&gt;Sceptre's built-in support for CloudFormation Change Sets&lt;/strong&gt; to decouple &lt;strong&gt;proposing changes&lt;/strong&gt; from &lt;strong&gt;executing them&lt;/strong&gt;. This gave us:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Change Set visibility before applying&lt;/li&gt;
&lt;li&gt;A file-based workflow for review and audit&lt;/li&gt;
&lt;li&gt;Controlled deployments aligned with ITIL practices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;👥 &lt;strong&gt;Who This Helps&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Engineers working in &lt;strong&gt;regulated environments&lt;/strong&gt; (finance, enterprise IT, healthcare)&lt;/li&gt;
&lt;li&gt;DevOps teams needing &lt;strong&gt;pre-deployment approvals&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Anyone trying to bridge &lt;strong&gt;automation with change control&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="technical-implementation"&gt;
&lt;h2&gt;⚙️ Technical Implementation&lt;/h2&gt;
&lt;p&gt;Let's visualize the change management workflow:&lt;/p&gt;
&lt;div class="mermaid" id="mermaid-diagram--1024360889188818916"&gt;
flowchart TD
   G[Git Repo] --&gt;|Template Changes| S[Sceptre]
   S --&gt;|Create| CS[Change Set]
   CS --&gt;|Review| A[Approval]
   A --&gt;|Execute| CF[CloudFormation]
   CF --&gt;|Update| I[Infrastructure]

   subgraph "Change Control Process"
   CS
   A
   end

   subgraph "AWS"
   CF
   I
   end&lt;/div&gt;&lt;div class="section" id="add-a-stop-lambda-to-your-cloudformation-template"&gt;
&lt;h3&gt;1️⃣ Add a Stop-Lambda to Your CloudFormation Template&lt;/h3&gt;
&lt;p&gt;For example, we extended our template with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;StopInstanceLambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;AWS::Lambda::Function&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;index.handler&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!GetAtt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;LambdaExecutionRole.Arn&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python3.9&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;30&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;ZipFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;import boto3&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;import os&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;def handler(event, context):&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;ec2 = boto3.client(&amp;#39;ec2&amp;#39;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;ec2.stop_instances(InstanceIds=[os.environ[&amp;#39;INSTANCE_ID&amp;#39;]])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;Variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;InstanceId&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We committed this change to Git but &lt;strong&gt;did not deploy yet&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="preview-with-sceptre-change-set"&gt;
&lt;h3&gt;2️⃣ Preview with Sceptre Change Set&lt;/h3&gt;
&lt;p&gt;In your Sceptre stack config (&lt;cite&gt;stop-tests.yaml&lt;/cite&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;template_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;start-stop-template.yaml&lt;/span&gt;
&lt;span class="nt"&gt;stack_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;start-tests-scheduler&lt;/span&gt;
&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;InstanceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;i-0123456789abcdef0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sceptre&lt;span class="w"&gt; &lt;/span&gt;create-change-set&lt;span class="w"&gt; &lt;/span&gt;dev/stop-tests.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates a &lt;strong&gt;named Change Set&lt;/strong&gt; in CloudFormation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="review-the-change-set"&gt;
&lt;h3&gt;3️⃣ Review the Change Set&lt;/h3&gt;
&lt;p&gt;You can now inspect the proposed changes via:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sceptre&lt;span class="w"&gt; &lt;/span&gt;describe-change-set&lt;span class="w"&gt; &lt;/span&gt;dev/stop-tests.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This outputs a diff-like summary of added/removed/modified resources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="execute-the-change-set-in-a-controlled-window"&gt;
&lt;h3&gt;4️⃣ Execute the Change Set in a Controlled Window&lt;/h3&gt;
&lt;p&gt;Once approved:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sceptre&lt;span class="w"&gt; &lt;/span&gt;execute-change-set&lt;span class="w"&gt; &lt;/span&gt;dev/stop-tests.yaml
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This &lt;strong&gt;applies only what was reviewed and approved&lt;/strong&gt;, nothing more.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting-debugging"&gt;
&lt;h2&gt;🛠️ Troubleshooting &amp;amp; Debugging&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Change Sets fail if resources are renamed instead of replaced — use &lt;cite&gt;Retain&lt;/cite&gt; policies or snapshots carefully.&lt;/li&gt;
&lt;li&gt;If nothing appears in the Change Set, verify your stack is actually different from the current state.&lt;/li&gt;
&lt;li&gt;Include &lt;cite&gt;--no-execute-changeset&lt;/cite&gt; in manual &lt;cite&gt;aws cloudformation&lt;/cite&gt; calls if testing outside Sceptre.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="itil-alignment-best-practices"&gt;
&lt;h2&gt;🔁 ITIL Alignment &amp;amp; Best Practices&lt;/h2&gt;
&lt;p&gt;Why this works for &lt;strong&gt;change management&lt;/strong&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;✅ &lt;strong&gt;Pre-approved changes&lt;/strong&gt;: Reviewable before execution&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Audit trail&lt;/strong&gt;: Change Set IDs + Git commits form a traceable chain&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Rollback-ready&lt;/strong&gt;: No impact until applied; easy to cancel&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Automatable&lt;/strong&gt;: Integrates with GitOps, CI/CD, and approval gates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Compare to a traditional ITIL CAB process:
- Sceptre's Change Set becomes the &lt;strong&gt;RFC payload&lt;/strong&gt;
- Execution timing maps to &lt;strong&gt;change windows&lt;/strong&gt;
- Logs &amp;amp; ChangeSet name tie into &lt;strong&gt;CMDB or ticketing systems&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion-takeaways"&gt;
&lt;h2&gt;✅ Conclusion &amp;amp; Takeaways&lt;/h2&gt;
&lt;p&gt;By using Sceptre's Change Sets, we introduced &lt;strong&gt;governed change control&lt;/strong&gt; without sacrificing automation. It's a clean way to blend &lt;strong&gt;DevOps practices&lt;/strong&gt; with &lt;strong&gt;ITIL compliance&lt;/strong&gt; — reducing risk while maintaining velocity.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comments-next-steps"&gt;
&lt;h2&gt;💬 Comments &amp;amp; Next Steps&lt;/h2&gt;
&lt;p&gt;Have you implemented similar change control processes in your AWS infrastructure? Share your experience or ask questions below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Cloud"></category><category term="aws"></category><category term="sceptre"></category><category term="itil"></category><category term="change-management"></category><category term="cloudformation"></category><category term="devops"></category><category term="compliance"></category><category term="infrastructure"></category></entry><entry><title>Enforcing GPG-Signed Commits in Git</title><link href="https://nunogrl.com/articles/enforcing-gpg-signed-commits-git/" rel="alternate"></link><published>2024-03-21T00:00:00+00:00</published><updated>2024-03-21T00:00:00+00:00</updated><author><name>Nuno Leitao</name></author><id>tag:nunogrl.com,2024-03-21:/articles/enforcing-gpg-signed-commits-git/</id><summary type="html">&lt;p class="first last"&gt;Learn how to enforce GPG-signed commits in Git to prevent commit impersonation and ensure code authenticity&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="problem-solution"&gt;
&lt;h2&gt;🚀 Problem &amp;amp; Solution&lt;/h2&gt;
&lt;div class="section" id="context-backstory"&gt;
&lt;h3&gt;📌 &lt;strong&gt;Context / Backstory&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In our collaborative development environment, we discovered that Git's flexibility allows commits under any name and email. This became a security concern when we realized that GitHub identifies users by email, not SSH keys.&lt;/p&gt;
&lt;div class="mermaid" id="mermaid-diagram-1842184663821928552"&gt;
sequenceDiagram
  participant Alice
  participant Bob
  participant GitHub

  Alice-&gt;&gt;GitHub: Commit signed with alice@example.com (GPG signed)
  GitHub-&gt;&gt;GitHub: Shows "Verified" commit from Alice

  Bob-&gt;&gt;GitHub: Commit using alice@example.com (no signature)
  GitHub-&gt;&gt;GitHub: Shows commit as from "Alice" (Unverified)

  Note over GitHub: GitHub matches commits by email, not by SSH or true identity.&lt;/div&gt;&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h3&gt;⚠️ &lt;strong&gt;The Problem&lt;/strong&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Anyone with repository access can spoof another contributor's identity&lt;/li&gt;
&lt;li&gt;Commit history could be manipulated without detection&lt;/li&gt;
&lt;li&gt;No cryptographic proof of commit authenticity&lt;/li&gt;
&lt;li&gt;GitHub's user identification relies solely on email addresses&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h3&gt;💡 &lt;strong&gt;The Solution&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We implemented mandatory GPG-signed commits, which:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Cryptographically verify commit authenticity&lt;/li&gt;
&lt;li&gt;Prevent identity spoofing&lt;/li&gt;
&lt;li&gt;Create traceable commit history&lt;/li&gt;
&lt;li&gt;Integrate with GitHub's verification system&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="who-this-helps"&gt;
&lt;h3&gt;👥 &lt;strong&gt;Who This Helps&lt;/strong&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Security-conscious development teams&lt;/li&gt;
&lt;li&gt;Open-source project maintainers&lt;/li&gt;
&lt;li&gt;Regulated environments requiring audit trails&lt;/li&gt;
&lt;li&gt;Organizations needing verified commit history&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="technical-implementation"&gt;
&lt;h2&gt;⚙️ Technical Implementation&lt;/h2&gt;
&lt;p&gt;Let's visualize the GPG signing workflow:&lt;/p&gt;
&lt;div class="mermaid" id="mermaid-diagram--3822165489569652770"&gt;
flowchart LR
   C[Commit] --&gt;|Sign| G[GPG Key]
   G --&gt;|Verify| GH[GitHub]

   subgraph "Local System"
   C
   G
   end

   subgraph "Remote"
   GH
   end

   style GH fill:#f96,stroke:#333
   style G fill:#9f6,stroke:#333&lt;/div&gt;&lt;div class="section" id="setting-up-gpg-keys"&gt;
&lt;h3&gt;1️⃣ Setting Up GPG Keys&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Generate a new GPG key&lt;/span&gt;
gpg&lt;span class="w"&gt; &lt;/span&gt;--full-generate-key

&lt;span class="c1"&gt;# List your keys&lt;/span&gt;
gpg&lt;span class="w"&gt; &lt;/span&gt;--list-secret-keys&lt;span class="w"&gt; &lt;/span&gt;--keyid-format&lt;span class="o"&gt;=&lt;/span&gt;long
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="configuring-git"&gt;
&lt;h3&gt;2️⃣ Configuring Git&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;Configure&lt;span class="w"&gt; &lt;/span&gt;Git&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;use&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;GPG&lt;span class="w"&gt; &lt;/span&gt;key
&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;user.signingkey&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;YOUR_KEY_ID&amp;gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;commit.gpgsign&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--global&lt;span class="w"&gt; &lt;/span&gt;gpg.program&lt;span class="w"&gt; &lt;/span&gt;gpg
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="github-integration"&gt;
&lt;h3&gt;3️⃣ GitHub Integration&lt;/h3&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Export your public GPG key:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;gpg&lt;span class="w"&gt; &lt;/span&gt;--armor&lt;span class="w"&gt; &lt;/span&gt;--export&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;YOUR_KEY_ID&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;Add the key to your GitHub account settings&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="enforcing-signed-commits"&gt;
&lt;h3&gt;4️⃣ Enforcing Signed Commits&lt;/h3&gt;
&lt;p&gt;In GitHub repository settings:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Navigate to Settings &amp;gt; Branches&lt;/li&gt;
&lt;li&gt;Add branch protection rule&lt;/li&gt;
&lt;li&gt;Enable &amp;quot;Require signed commits&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting-debugging"&gt;
&lt;h2&gt;🛠️ Troubleshooting &amp;amp; Debugging&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;GPG signing fails&lt;/strong&gt;: Check &lt;cite&gt;gpg-agent&lt;/cite&gt; configuration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub doesn't show &amp;quot;Verified&amp;quot;&lt;/strong&gt;: Ensure GPG key is added to GitHub&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD issues&lt;/strong&gt;: Set up proper &lt;cite&gt;GNUPGHOME&lt;/cite&gt; environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smart card/YubiKey&lt;/strong&gt;: Verify proper card reader access&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="optimizations-best-practices"&gt;
&lt;h2&gt;🔁 Optimizations &amp;amp; Best Practices&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Use GPG subkeys instead of master keys&lt;/li&gt;
&lt;li&gt;Implement regular key rotation&lt;/li&gt;
&lt;li&gt;Set up separate signing keys for different contexts&lt;/li&gt;
&lt;li&gt;Use environment isolation in CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Consider hardware security keys (YubiKey) for key storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion-takeaways"&gt;
&lt;h2&gt;✅ Conclusion &amp;amp; Takeaways&lt;/h2&gt;
&lt;p&gt;GPG-signed commits provide a robust security layer for Git workflows, ensuring:
- Verified commit authenticity
- Protected repository history
- Clear accountability
- Compliance with security best practices&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comments-next-steps"&gt;
&lt;h2&gt;💬 Comments &amp;amp; Next Steps&lt;/h2&gt;
&lt;p&gt;How do you handle commit verification in your organization? Share your experience or ask questions below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Infrastructure Security"></category><category term="git"></category><category term="gpg"></category><category term="security"></category><category term="devops"></category><category term="version-control"></category><category term="cryptography"></category><category term="authentication"></category></entry><entry><title>Password Store with GPG and Git</title><link href="https://nunogrl.com/articles/password-store-gpg-git/" rel="alternate"></link><published>2024-03-21T00:00:00+00:00</published><updated>2024-03-21T00:00:00+00:00</updated><author><name>Nuno Leitao</name></author><id>tag:nunogrl.com,2024-03-21:/articles/password-store-gpg-git/</id><summary type="html">&lt;p class="first last"&gt;Learn how to set up a secure, Git-based password management system using password-store and GPG encryption&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="problem-solution"&gt;
&lt;h2&gt;🚀 Problem &amp;amp; Solution&lt;/h2&gt;
&lt;div class="section" id="context-backstory"&gt;
&lt;h3&gt;📌 &lt;strong&gt;Context / Backstory&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We needed a secure way to manage passwords and secrets across multiple servers and team members. Commercial password managers were either too complex, costly, or required external services we wanted to avoid.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h3&gt;⚠️ &lt;strong&gt;The Problem&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Managing secrets across systems and teams presents several challenges:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Keeping passwords secure yet accessible&lt;/li&gt;
&lt;li&gt;Tracking changes and maintaining history&lt;/li&gt;
&lt;li&gt;Sharing secrets securely between team members&lt;/li&gt;
&lt;li&gt;Avoiding dependency on external services&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-solution"&gt;
&lt;h3&gt;💡 &lt;strong&gt;The Solution&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We implemented &lt;cite&gt;password-store&lt;/cite&gt; with GPG encryption and Git integration, providing:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Secure GPG encryption for all secrets&lt;/li&gt;
&lt;li&gt;Git-based version control and distribution&lt;/li&gt;
&lt;li&gt;Fully local operation with no external dependencies&lt;/li&gt;
&lt;li&gt;Command-line interface for automation&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="who-this-helps"&gt;
&lt;h3&gt;👥 &lt;strong&gt;Who This Helps&lt;/strong&gt;&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;System administrators managing multiple servers&lt;/li&gt;
&lt;li&gt;DevOps teams handling shared credentials&lt;/li&gt;
&lt;li&gt;Security-conscious users wanting local password management&lt;/li&gt;
&lt;li&gt;Teams needing version-controlled secrets&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="technical-implementation"&gt;
&lt;h2&gt;⚙️ Technical Implementation&lt;/h2&gt;
&lt;p&gt;Let's visualize the password-store workflow:&lt;/p&gt;
&lt;div class="mermaid" id="mermaid-diagram-5400457694691543625"&gt;
flowchart LR
   P[Password] --&gt;|Encrypt| G[GPG]
   G --&gt;|Store| PS[password-store]
   PS --&gt;|Version| Git[Git Repository]
   Git --&gt;|Sync| T[Team Members]

   subgraph "Local System"
   P
   G
   PS
   end

   subgraph "Distribution"
   Git
   T
   end&lt;/div&gt;&lt;div class="section" id="generating-gpg-keys-in-batch-mode"&gt;
&lt;h3&gt;1️⃣ Generating GPG Keys in Batch Mode&lt;/h3&gt;
&lt;p&gt;For automated environments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;gpg-server-key.conf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;
&lt;span class="s"&gt;%no-protection&lt;/span&gt;
&lt;span class="s"&gt;Key-Type: default&lt;/span&gt;
&lt;span class="s"&gt;Subkey-Type: default&lt;/span&gt;
&lt;span class="s"&gt;Name-Real: Server Automation Key&lt;/span&gt;
&lt;span class="s"&gt;Name-Email: server@example.com&lt;/span&gt;
&lt;span class="s"&gt;Expire-Date: 0&lt;/span&gt;
&lt;span class="s"&gt;%commit&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;

gpg&lt;span class="w"&gt; &lt;/span&gt;--batch&lt;span class="w"&gt; &lt;/span&gt;--generate-key&lt;span class="w"&gt; &lt;/span&gt;gpg-server-key.conf
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-up-the-environment"&gt;
&lt;h3&gt;2️⃣ Setting Up the Environment&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PASSWORD_STORE_GPG_OPTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--armor&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GNUPGHOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/password-store/.gnupg
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PASSWORD_STORE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/password-store/store
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="initializing-the-password-store"&gt;
&lt;h3&gt;3️⃣ Initializing the Password Store&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$GNUPGHOME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_STORE_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$GNUPGHOME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_STORE_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
pass&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;server@example.com
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="git-integration"&gt;
&lt;h3&gt;4️⃣ Git Integration&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_STORE_DIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;init
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.
git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Initial password store&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;git@example.com:secrets.git
git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;main
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="troubleshooting-debugging"&gt;
&lt;h2&gt;🛠️ Troubleshooting &amp;amp; Debugging&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Ensure proper GPG key permissions (700 for directories, 600 for files)&lt;/li&gt;
&lt;li&gt;Verify GPG recipient when encryption fails&lt;/li&gt;
&lt;li&gt;Check Git remote access rights for sync issues&lt;/li&gt;
&lt;li&gt;Monitor Git conflicts when multiple users update simultaneously&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="optimizations-alternatives"&gt;
&lt;h2&gt;🔁 Optimizations &amp;amp; Alternatives&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Consider using GPG agent for improved key handling&lt;/li&gt;
&lt;li&gt;Implement Git hooks for pre-commit validation&lt;/li&gt;
&lt;li&gt;Use Git branches for testing password updates&lt;/li&gt;
&lt;li&gt;Consider &lt;cite&gt;pass&lt;/cite&gt; extensions for additional features&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion-takeaways"&gt;
&lt;h2&gt;✅ Conclusion &amp;amp; Takeaways&lt;/h2&gt;
&lt;p&gt;Using GPG with password-store provides a &lt;strong&gt;flexible, secure, and lightweight&lt;/strong&gt; method for managing secrets across machines. With Git integration, you get version history, team sharing, and distributed backup—&lt;strong&gt;without compromising security&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comments-next-steps"&gt;
&lt;h2&gt;💬 Comments &amp;amp; Next Steps&lt;/h2&gt;
&lt;p&gt;How do you manage shared secrets in your infrastructure? Share your experience or ask questions below!&lt;/p&gt;
&lt;/div&gt;
</content><category term="Infrastructure Security"></category><category term="gpg"></category><category term="git"></category><category term="password-management"></category><category term="security"></category><category term="encryption"></category><category term="devops"></category><category term="secrets-management"></category></entry></feed>