1<!DOCTYPE html><html lang="en"><head><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="true"/><style>undefined</style><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5"/><meta charSet="utf-8"/><title>Latest posts</title><link rel="apple-touch-icon" sizes="180x180" href="/images/favicon.png"/><meta name="description" content="Smallfries Digital is an AWS cloud consultancy specializing in DevOps, cloud security, disaster recovery, data platforms, networking and AI/ML solutions for modern enterprises."/><meta name="author" content="Smallfries Digital Inc"/><meta property="og:title" content="Latest posts"/><meta property="og:description" content="Smallfries Digital is an AWS cloud consultancy specializing in DevOps, cloud security, disaster recovery, data platforms, networking and AI/ML solutions for modern enterprises."/><meta property="og:type" content="website"/><meta property="og:url" content="https://www.smallfries.digital/blogs"/><meta name="twitter:title" content="Latest posts"/><meta name="twitter:description" content="Smallfries Digital is an AWS cloud consultancy specializing in DevOps, cloud security, disaster recovery, data platforms, networking and AI/ML solutions for modern enterprises."/><meta property="og:image" content="https://www.smallfries.digital"/><meta name="twitter:image" content="https://www.smallfries.digital"/><meta name="twitter:card" content="summary_large_image"/><link rel="preload" as="image" imageSrcSet="/_next/image?url=%2Fimages%2Flogo.png&w=640&q=75 1x, /_next/image?url=%2Fimages%2Flogo.png&w=828&q=75 2x"/><link rel="preload" as="image" href="/images/presigned-url-architecture.svg"/><meta name="next-head-count" content="19"/><link rel="shortcut icon" type="image/png" sizes="32x32" href="/images/favicon.png"/><meta name="theme-name" content="next-boilerplate"/><meta name="msapplication-TileColor" content="#000000"/><meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff"/><meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000"/><link rel="preload" href="/_next/static/css/5d97e057b48d9a27.css" as="style"/><link rel="stylesheet" href="/_next/static/css/5d97e057b48d9a27.css" data-n-g=""/><noscript data-n-css=""></noscript>
1<script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script>
1<script src="/_next/static/chunks/webpack-8fa1640cc84ba8fe.js" defer=""></script>
1<script src="/_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script>
1<script src="/_next/static/chunks/main-0c597b21730512df.js" defer=""></script>
1<script src="/_next/static/chunks/pages/_app-a01a74e130074b8a.js" defer=""></script>
1<script src="/_next/static/chunks/ae51ba48-b146860dfe661311.js" defer=""></script>
1<script src="/_next/static/chunks/127-d83b59153ab2a442.js" defer=""></script>
1<script src="/_next/static/chunks/192-eba45a982d461e2f.js" defer=""></script>
1<script src="/_next/static/chunks/pages/blogs-19300012d7fade99.js" defer=""></script>
1<script src="/_next/static/MheQSXCJqjkF0KAoqGagX/_buildManifest.js" defer=""></script>
1<script src="/_next/static/MheQSXCJqjkF0KAoqGagX/_ssgManifest.js" defer=""></script>
1</head><body><div id="__next"><header class="header"><nav class="navbar container"><div class="order-0"><a class="navbar-brand block py-1" style="height:481%;width:200px" href="https://www.smallfries.digital"><img alt="Smallfries Digital â AWS Cloud Consultancy | DevOps, Security & AI" width="400" height="96" decoding="async" data-nimg="1" style="color:transparent" srcSet="/_next/image?url=%2Fimages%2Flogo.png&w=640&q=75 1x, /_next/image?url=%2Fimages%2Flogo.png&w=828&q=75 2x" src="/_next/image?url=%2Fimages%2Flogo.png&w=828&q=75"/></a></div><button id="show-button" class="order-2 flex cursor-pointer items-center md:hidden md:order-1"><svg class="h-6 fill-current" viewBox="0 0 20 20"><title>Menu Close</title><path d="M0 3h20v2H0V3z m0 6h20v2H0V9z m0 6h20v2H0V0z"></path></svg></button><div id="nav-menu" class="order-3 md:order-1 max-h-0"><ul class="navbar-nav block w-full md:flex md:w-auto lg:space-x-2"><li class="nav-item"><a class="nav-link block " href="/">Home</a></li><li class="nav-item"><a class="nav-link block " href="/products">Products</a></li><li class="nav-item"><a class="nav-link block nav-link-active" href="/blogs">Blog</a></li><li class="nav-item"><a class="nav-link block " href="/pricing">Pricing</a></li><li class="nav-item"><a class="nav-link block " href="/about">About Us</a></li><li class="nav-item"><a class="nav-link block " href="/contact">Contact</a></li><li class="md:hidden"><a class="btn btn-primary z-0 py-[14px]" rel="" href="/pricing">Get Started</a></li></ul></div><div class="d-flex order-1 ml-auto hidden min-w-[200px] items-center justify-end md:ml-0 md:flex md:order-2"><a class="btn btn-primary z-0 py-[14px]" rel="" href="/pricing">Get Started</a></div></nav></header><main><section class="section"><div class="container"><h1 class="h1 text-center font-normal text-[56px]">Latest posts</h1><div class="section row pb-0"><div class="col-12 pb-12 lg:pb-24"><div class="row items-center"><div class="col-12 md:col-6"><img alt="Scheduled generation of a pre-signed upload URL with AWS Lambda, EventBridge and Boto3" width="540" height="227" decoding="async" data-nimg="1" class="h-auto w-full rounded-lg" style="color:transparent" src="/images/presigned-url-architecture.svg"/></div><div class="col-12 md:col-6"><h2 class="h3 mb-2 mt-4"><a class="block hover:text-primary" href="/blogs/presigned-url-lambda">Scheduled generation of a pre-signed upload URL with AWS Lambda, EventBridge and Boto3</a></h2><p class="text-text"> By Andrzej Komarnicki Cloud DevOps @ smallfries.digital 2Date: 2024-07-01 Pre-signed URLs ar</p><a class="btn btn-primary mt-4" rel="" href="/blogs/presigned-url-lambda">Read More</a></div></div></div><div class="col-12 mb-8 sm:col-6 lg:col-4"><img alt="VPC Endpoints on-demand with CloudFormation" loading="lazy" width="925" height="475" decoding="async" data-nimg="1" class="rounded-lg" style="color:transparent" srcSet="/_next/image?url=%2Fimages%2Fvpc-endpoints-designer.png&w=1080&q=75 1x, /_next/image?url=%2Fimages%2Fvpc-endpoints-designer.png&w=1920&q=75 2x" src="/_next/image?url=%2Fimages%2Fvpc-endpoints-designer.png&w=1920&q=75"/><h2 class="h3 mb-2 mt-4"><a class="block hover:text-primary" href="/blogs/vpcendpoints-cloudformation">VPC Endpoints on-demand with CloudFormation</a></h2><p class="text-text"></p><a class="btn btn-primary mt-4" rel="" href="/blogs/vpcendpoints-cloudformation">Read More</a></div><div class="col-12 mb-8 sm:col-6 lg:col-4"><img alt="Generate a custom type-safe Helm Chart construct with cdk8s" loading="lazy" width="445" height="230" decoding="async" data-nimg="1" class="rounded-lg" style="color:transparent" srcSet="/_next/image?url=%2Fimages%2Fcdk8sblog.png&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fcdk8sblog.png&w=1080&q=75 2x" src="/_next/image?url=%2Fimages%2Fcdk8sblog.png&w=1080&q=75"/><h2 class="h3 mb-2 mt-4"><a class="block hover:text-primary" href="/blogs/cdk8s-helm-import">Generate a custom type-safe Helm Chart construct with cdk8s</a></h2><p class="text-text"></p><a class="btn btn-primary mt-4" rel="" href="/blogs/cdk8s-helm-import">Read More</a></div><div class="col-12 mb-8 sm:col-6 lg:col-4"><img alt="Nested API routes with Node.js AWS Lambda Function URL, Middy HTTP router and AWS CDK" loading="lazy" width="445" height="230" decoding="async" data-nimg="1" class="rounded-lg" style="color:transparent" srcSet="/_next/image?url=%2Fimages%2Fblogpostmiddyroutescdk.png&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fblogpostmiddyroutescdk.png&w=1080&q=75 2x" src="/_next/image?url=%2Fimages%2Fblogpostmiddyroutescdk.png&w=1080&q=75"/><h2 class="h3 mb-2 mt-4"><a class="block hover:text-primary" href="/blogs/apiroutes-nodejslambda-middy">Nested API routes with Node.js AWS Lambda Function URL, Middy HTTP router and AWS CDK</a></h2><p class="text-text"></p><a class="btn btn-primary mt-4" rel="" href="/blogs/apiroutes-nodejslambda-middy">Read More</a></div></div></div></section></main><footer class="section bg-slate-900 pb-0"><div class="container"><div class="row"><div class="mb-12 sm:col-6 lg:col-3"><h2 class="h4 text-white">Company</h2><ul class="mt-6"><li class="mb-2"><a rel="" class="text-slate-400 transition-colors duration-200 hover:text-white" href="/pricing">Pricing</a></li></ul></div><div class="mb-12 sm:col-6 lg:col-3"><ul class="mt-6"><li class="mb-2"><a rel="" class="text-slate-400 transition-colors duration-200 hover:text-white" href="/privacy">Privacy Policy</a></li><li class="mb-2"><a rel="" class="text-slate-400 transition-colors duration-200 hover:text-white" href="/terms">Terms & Conditions</a></li></ul></div><div class="md-12 sm:col-6 lg:col-3"><a aria-label="Smallfries Digital home" href="/"><img alt="Smallfries Digital logo" loading="lazy" width="200" height="48" decoding="async" data-nimg="1" class="" style="color:transparent" srcSet="/_next/image?url=%2Fimages%2Flogo.png&w=256&q=75 1x, /_next/image?url=%2Fimages%2Flogo.png&w=640&q=75 2x" src="/_next/image?url=%2Fimages%2Flogo.png&w=640&q=75"/></a><p class="mt-3 mb-6 text-slate-400">© Smallfries Digital Inc. All rights reserved. 2026.</p><ul class="social-icons mb-8"><li class="inline-block"><a aria-label="linkedin" href="https://www.linkedin.com/company/smallfries-digital" target="_blank" rel="noopener noreferrer nofollow"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M444.17 32H70.28C49.85 32 32 46.7 32 66.89v374.72C32 461.91 49.85 480 70.28 480h373.78c20.54 0 35.94-18.21 35.94-38.39V66.89C480.12 46.7 464.6 32 444.17 32zm-273.3 373.43h-64.18V205.88h64.18zM141 175.54h-.46c-20.54 0-33.84-15.29-33.84-34.43 0-19.49 13.65-34.42 34.65-34.42s33.85 14.82 34.31 34.42c-.01 19.14-13.31 34.43-34.66 34.43zm264.43 229.89h-64.18V296.32c0-26.14-9.34-44-32.56-44-17.74 0-28.24 12-32.91 23.69-1.75 4.2-2.22 9.92-2.22 15.76v113.66h-64.18V205.88h64.18v27.77c9.34-13.3 23.93-32.44 57.88-32.44 42.13 0 74 27.77 74 87.64z"></path></svg></a></li><li class="inline-block"><a aria-label="github" href="https://github.com/smallfriesdigital" target="_blank" rel="noopener noreferrer nofollow"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M256 32C132.3 32 32 134.9 32 261.7c0 101.5 64.2 187.5 153.2 217.9a17.56 17.56 0 003.8.4c8.3 0 11.5-6.1 11.5-11.4 0-5.5-.2-19.9-.3-39.1a102.4 102.4 0 01-22.6 2.7c-43.1 0-52.9-33.5-52.9-33.5-10.2-26.5-24.9-33.6-24.9-33.6-19.5-13.7-.1-14.1 1.4-14.1h.1c22.5 2 34.3 23.8 34.3 23.8 11.2 19.6 26.2 25.1 39.6 25.1a63 63 0 0025.6-6c2-14.8 7.8-24.9 14.2-30.7-49.7-5.8-102-25.5-102-113.5 0-25.1 8.7-45.6 23-61.
26-2.3-5.8-10-29.2 2.2-60.8a18.64 18.64 0 015-.5c8.1 0 26.4 3.1 56.6 24.1a208.21 208.21 0 01112.2 0c30.2-21 48.5-24.1 56.6-24.1a18.64 18.64 0 015 .5c12.2 31.6 4.5 55 2.2 60.8 14.3 16.1 23 36.6 23 61.6 0 88.2-52.4 107.6-102.3 113.3 8 7.1 15.2 21.1 15.2 42.5 0 30.7-.3 55.5-.3 63 0 5.4 3.1 11.5 11.4 11.5a19.35 19.35 0 004-.4C415.9 449.2 480 363.1 480 261.7 480 134.9 379.7 32 256 32z"></path></svg></a></li></ul></div></div><div class="border-t border-slate-700/50 py-6"><p class="text-sm text-center text-slate-500">Powered by Next.js</p></div></div></footer></div>
2<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"pagination":4,"posts":[{"frontmatter":{"title":"Scheduled generation of a pre-signed upload URL with AWS Lambda, EventBridge and Boto3","description":"Use Amazon EventBridge and AWS Lambda with Python Boto3 to generate time-limited pre-signed S3 upload URLs on a schedule and deliver them via Amazon SNS","image":"/images/presigned-url-architecture.svg","date":"2026-04-22T05:00:00.000Z","draft":false},"slug":"presigned-url-lambda","content":"\n\n\n\u003cdd\u003e By Andrzej Komarnicki \u003cbr /\u003e Cloud DevOps @ [smallfries.digital](https://smallfries.digital) \u003cbr /\u003e\nDate: 2024-07-01 \u003c/dd\u003e\n\nPre-signed URLs are a powerful S3 feature that lets you grant temporary, scoped access to upload or download objects without exposing your AWS credentials or making a bucket public. When combined with a scheduled Lambda function, you can automate the generation and delivery of these URLs on a recurring basis â useful for scenarios like periodic data ingestion from external partners, scheduled report uploads, or any workflow where a third party needs time-limited write access to your S3 bucket.\n\nIn this post we'll build a solution where Amazon EventBridge triggers an AWS Lambda function on a schedule, the function generates a pre-signed PUT URL for a specific S3 key, and then publishes that URL to an Amazon SNS topic so it can be delivered via email, Slack or any other subscriber.\n\n\n\n#### How pre-signed URLs work\n\nWhen you call `generate_presigned_url()` via Boto3, the S3 client signs the request using your Lambda function's IAM credentials. The resulting URL encodes the bucket, key, expiration and signature as query parameters. Anyone with the URL can perform the allowed operation (in our case `put_object`) until the URL expires â no AWS credentials required on their end.\n\n\u003e **Security note:** The pre-signed URL inherits the permissions of the IAM principal that generated it. Make sure your Lambda execution role has only the minimum required S3 permissions, and keep expiration times as short as practical.\n\n#### The Lambda function\n\nLet's start with the Lambda handler. This function generates a pre-signed PUT URL and publishes it to an SNS topic.\n\n```python\nimport os\nimport json\nimport boto3\nfrom datetime import datetime, timezone\n\ns3_client = boto3.client(\"s3\")\nsns_client = boto3.client(\"sns\")\n\nBUCKET_NAME = os.environ[\"BUCKET_NAME\"]\nSNS_TOPIC_ARN = os.environ[\"SNS_TOPIC_ARN\"]\nURL_EXPIRATION = int(os.environ.get(\"URL_EXPIRATION\", 3600)) # seconds\nUPLOAD_PREFIX = os.environ.get(\"UPLOAD_PREFIX\", \"uploads\")\n\n\ndef handler(event, context):\n \"\"\"Generate a pre-signed S3 upload URL and publish it via SNS.\"\"\"\n\n # Build a unique object key using the current UTC timestamp\n timestamp = datetime.now(timezone.utc).strftime(\"%Y-%m-%d_%H%M%S\")\n object_key = f\"{UPLOAD_PREFIX}/{timestamp}.dat\"\n\n # Generate the pre-signed PUT URL\n presigned_url = s3_client.generate_presigned_url(\n ClientMethod=\"put_object\",\n Params={\n \"Bucket\": BUCKET_NAME,\n \"Key\": object_key,\n \"ContentType\": \"application/octet-stream\",\n },\n ExpiresIn=URL_EXPIRATION,\n )\n\n # Publish the URL to SNS\n message = (\n f\"A new pre-signed upload URL has been generated.\\n\\n\"\n f\"Bucket: {BUCKET_NAME}\\n\"\n f\"Key: {object_key}\\n\"\n f\"Expires in: {URL_EXPIRATION // 60} minutes\\n\\n\"\n f\"Upload URL:\\n{presigned_url}\\n\\n\"\n f\"Example upload with curl:\\n\"\n f'curl -X PUT -H \"Content-Type: application/octet-stream\" '\n f'--data-binary @yourfile.dat \"{presigned_url}\"'\n )\n\n sns_client.publish(\n TopicArn=SNS_TOPIC_ARN,\n Subject=f\"S3 Upload URL - {timestamp}\",\n Message=message,\n )\n\n return {\n \"statusCode\": 200,\n \"body\": json.dumps({\n \"bucket\": BUCKET_NAME,\n \"key\": object_key,\n \"expires_in\": URL_EXPIRATION,\n }),\n }\n```\n\nA few things to note:\n\n- We use `put_object` as the `ClientMethod` because we want to grant upload (write) access, not download.\n- The `ContentType` parameter in `Param
2s` ensures the uploader must send the correct content type header.\n- The object key includes a UTC timestamp so each scheduled invocation produces a unique key.\n- `URL_EXPIRATION` defaults to 3600 seconds (1 hour) but is configurable via environment variable.\n\n#### The EventBridge schedule\n\nAmazon EventBridge (formerly CloudWatch Events) lets you trigger Lambda functions on a cron or rate schedule. For example, to generate a new upload URL every day at 8:00 AM UTC:\n\n```bash\naws events put-rule \\\n --name \"daily-presigned-url\" \\\n --schedule-expression \"cron(0 8 * * ? *)\" \\\n --state ENABLED \\\n --description \"Trigger pre-signed URL generation daily at 08:00 UTC\"\n```\n\nThen add the Lambda function as a target:\n\n```bash\naws events put-targets \\\n --rule \"daily-presigned-url\" \\\n --targets \"Id\"=\"1\",\"Arn\"=\"arn:aws:lambda:us-east-1:123456789012:function:presigned-url-generator\"\n```\n\nDon't forget to grant EventBridge permission to invoke your Lambda:\n\n```bash\naws lambda add-permission \\\n --function-name presigned-url-generator \\\n --statement-id eventbridge-daily \\\n --action lambda:InvokeFunction \\\n --principal events.amazonaws.com \\\n --source-arn arn:aws:events:us-east-1:123456789012:rule/daily-presigned-url\n```\n\nYou can also use a simple rate expression if you prefer:\n\n```bash\n--schedule-expression \"rate(12 hours)\"\n```\n\n#### IAM policy for the Lambda execution role\n\nThe Lambda function needs permission to generate pre-signed URLs (which requires `s3:PutObject` on the target bucket/prefix) and to publish to the SNS topic:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"s3:PutObject\",\n \"Resource\": \"arn:aws:s3:::my-upload-bucket/uploads/*\"\n },\n {\n \"Effect\": \"Allow\",\n \"Action\": \"sns:Publish\",\n \"Resource\": \"arn:aws:sns:us-east-1:123456789012:presigned-url-topic\"\n }\n ]\n}\n```\n\n\u003e **Important:** Pre-signed URLs will only work if the IAM role that generated them still has the required permissions at the time the URL is used. If you revoke `s3:PutObject` from the role, all outstanding pre-signed URLs become invalid immediately.\n\n#### Testing locally\n\nYou can invoke the function locally with a test event to verify it works before deploying the schedule:\n\n```bash\naws lambda invoke \\\n --function-name presigned-url-generator \\\n --payload '{}' \\\n response.json\n\ncat response.json\n```\n\nYou should receive an SNS notification with the pre-signed URL. Test the upload with curl:\n\n```bash\ncurl -X PUT \\\n -H \"Content-Type: application/octet-stream\" \\\n --data-binary @testfile.dat \\\n \"\u003cpresigned-url-from-notification\u003e\"\n```\n\nThen verify the object landed in S3:\n\n```bash\naws s3 ls s3://my-upload-bucket/uploads/\n```\n\n#### Considerations\n\n- **URL expiration:** Keep it as short as practical. For a daily schedule, 1-2 hours is usually sufficient. Longer expirations increase the window of exposure.\n- **Object key collisions:** The timestamp-based key pattern avoids collisions for schedules down to one-second granularity. For sub-second invocations, consider adding a UUID.\n- **Encryption:** If your bucket uses SSE-KMS, the pre-signed URL signer also needs `kms:GenerateDataKey` permission, and the uploader's request will be encrypted transparently.\n- **CORS:** If the upload will happen from a browser, configure CORS on the S3 bucket to allow PUT requests from the appropriate origin.\n- **Monitoring:** Add CloudWatch alarms on Lambda errors and SNS delivery failures to catch issues early.\n\n#### Conclusion\n\nCombining EventBridge schedules with Lambda and Boto3's `generate_presigned_url()` gives you a clean, serverless pattern for recurring secure file uploads. The entire solution runs without servers to manage, scales automatically, and costs virtually nothing at low invocation volumes. You can extend this pattern by adding S3 event notifications to trigger downstream processing when the upload completes, or by integrating with Step Functions for more complex workflows.\n"},{"frontmatter":{"title":"VPC Endpoints on-demand with CloudFormation","description":".","image":"/images/vpc-endpoints-designer.png","date":"2024-0
27-01T05:00:00.000Z","draft":false},"slug":"vpcendpoints-cloudformation","content":"\n\n\n\u003cdd\u003e By Andrzej Komarnicki \u003cbr /\u003e Cloud DevOps @ [smallfries.digital](https://smallfries.digital) \u003cbr /\u003e\nDate: 2024-07-01 \u003c/dd\u003e\n\nIn modern cloud architectures, it's essential to secure communication between your resources within a Virtual Private Cloud (VPC) and other AWS services. VPC Endpoints provide a secure and scalable way to connect your VPC to AWS services without traversing the public internet, reducing the risk of data exposure and improving network performance.\n\nThis blog post will walk you through a CloudFormation template that creates multiple VPC Endpoints for popular AWS services like Amazon S3, DynamoDB, CloudWatch Logs, and AWS Systems Manager (SSM). We'll also cover the necessary security groups and policies to control access to these endpoints.\n\n#### [The CloudFormation Template](https://github.com/AndrzejKomarnicki/cloudformation_utils/blob/main/vpc-endpoints/vpcendpoints.yaml)\n\nThe provided YAML template creates the following resources:\n\n- Gateway VPC Endpoints: For Amazon S3 and DynamoDB, allowing private connectivity from your VPC to these services.\n\n- Interface VPC Endpoints: For CloudWatch Monitoring, CloudWatch Logs, SSM, and SSM Messages, enabling private access to these services from your VPC.\n\n- Security Groups: A default security group for the interface endpoints, and a separate security group for EC2 Instance Connect.\n\nHere's a breakdown of the key sections in the template:\n\n```yaml\nParameters:\n VpcId:\n Type: String\n Description: VPC ID\n VpcCIDR:\n Type: String\n Description: VPC CIDR to associate with VPC Endpoint security group\n SubnetIds:\n Type: List\u003cString\u003e\n Description: Comma-separated list of subnet IDs\n RouteTableIds:\n Type: List\u003cString\u003e\n Description: Comma-separated list of route table IDs\n```\n\nThe template accepts four parameters: the VPC ID, the VPC CIDR block (optional), a list of subnet IDs, and a list of route table IDs. These parameters allow you to customize the deployment to your specific VPC and network configuration.\n\n```yaml\nResources:\n VPCEndpointSecurityGroup:\n Type: AWS::EC2::SecurityGroup\n Properties:\n GroupDescription: VPC Endpoint Security Group\n VpcId: !Ref VpcId\n```\n\nThis section creates a security group for the interface VPC Endpoints. You can modify the security group rules to control inbound and outbound traffic to these endpoints.\n\n```yaml\nS3Endpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - s3:*\n Resource:\n - \"*\"\n RouteTableIds: !Ref RouteTableIds\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.s3\"\n VpcId: !Ref VpcId\n VpcEndpointType: Gateway\n PrivateDnsEnabled: false\n```\n\nThis resource creates a Gateway VPC Endpoint for Amazon S3. Note the PolicyDocument property, which allows all actions _(s3:\\*)_ on all S3 resources _(\\*)_. You can modify this policy to restrict access as needed.\n\nThe template includes similar resources for creating VPC Endpoints for DynamoDB, CloudWatch Monitoring, CloudWatch Logs, SSM, and SSM Messages.\n\n##### Deploying the Template\n\nTo deploy this CloudFormation template, you'll need to provide the required parameters (VPC ID, subnet IDs, and route table IDs) specific to your AWS account and VPC configuration. You can deploy the template using the AWS Management Console, AWS CLI, or AWS CloudFormation APIs.\n\nOnce deployed, your VPC will have secure, private connectivity to the specified AWS services, without the need for internet gateways, NAT gateways, or public IP addresses.\n\n#### Conclusion\n\nVPC Endpoints are a powerful tool for securing communication between your VPC resources and AWS services. By using CloudFormation templates like the one provided, you can streamline the deployment of VPC Endpoints across multiple accounts and environments, ensuring consistent and secure network configurations.\n\nRemember to review and customize the security group rules and endpoint policies to align with your organization's security requirements and access controls. Additionally, consider automating the deployment process using CI/CD pipelines or infrastru
2cture-as-code tools for efficient and repeatable infrastructure provisioning.\n\n##### Original Complete Template:\n\n```yaml\nAWSTemplateFormatVersion: 2010-09-09\nDescription: Create Gateway VPC Endpoints for S3 and DynamoDB. Interface VPC Endpoints for CloudWatch Monitoring/Logs, EC2 Instance Connect and SSM with a default SG.\nParameters:\n VpcId:\n Type: String\n Description: VPC ID\n VpcCIDR:\n Type: String\n Description: VPC CIDR to associate with VPC Endpoint security group (if not provided 0.0.0.0/0 will be used instead)\n SubnetIds:\n Type: List\u003cString\u003e\n Description: Comma separated list of subnet IDs\n RouteTableIds:\n Type: List\u003cString\u003e\n Description: Comma separated list of route table IDs\nConditions:\n HasVpcCIDR: !Not\n - !Equals\n - \"\"\n - !Ref VpcCIDR\nResources:\n VPCEndpointSecurityGroup:\n Type: AWS::EC2::SecurityGroup\n Properties:\n GroupDescription: VPC Endpoint Security Group\n VpcId: !Ref VpcId\n VPCEndpointSecurityGroupIngress:\n Type: AWS::EC2::SecurityGroupIngress\n Properties:\n GroupId: !Ref VPCEndpointSecurityGroup\n FromPort: 443\n ToPort: 443\n IpProtocol: tcp\n SourceSecurityGroupId: !Ref VPCEndpointSecurityGroup\n VPCEndpointSecurityGroupIngress2:\n Type: AWS::EC2::SecurityGroupIngress\n Properties:\n GroupId: !Ref VPCEndpointSecurityGroup\n FromPort: 443\n ToPort: 443\n IpProtocol: tcp\n CidrIp: !If\n - HasVpcCIDR\n - !Ref VpcCIDR\n - \"0.0.0.0/0\"\n S3Endpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - s3:*\n Resource:\n - \"*\"\n RouteTableIds: !Ref RouteTableIds\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.s3\"\n VpcId: !Ref VpcId\n VpcEndpointType: Gateway\n PrivateDnsEnabled: false\n DynamoDbEndpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - dynamodb:*\n Resource:\n - \"*\"\n RouteTableIds: !Ref RouteTableIds\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.dynamodb\"\n VpcId: !Ref VpcId\n VpcEndpointType: Gateway\n PrivateDnsEnabled: false\n CloudWatchEndpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - logs:*\n Resource:\n - \"*\"\n SubnetIds: !Ref SubnetIds\n SecurityGroupIds: !Split\n - \",\"\n - !Join\n - \",\"\n - - !GetAtt VPCEndpointSecurityGroup.GroupId\n VpcId: !Ref VpcId\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.logs\"\n VpcEndpointType: Interface\n PrivateDnsEnabled: false\n MonitoringEndpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - cloudwatch:*\n Resource:\n - \"*\"\n SubnetIds: !Ref SubnetIds\n SecurityGroupIds: !Split\n - \",\"\n - !Join\n - \",\"\n - - !GetAtt VPCEndpointSecurityGroup.GroupId\n VpcId: !Ref VpcId\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.monitoring\"\n VpcEndpointType: Interface\n PrivateDnsEnabled: false\n SsmEndpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - ssm:*\n Resource:\n - \"*\"\n SubnetIds: !Ref SubnetIds\n SecurityGroupIds: !Split\n - \",\"\n - !Join\n - \",\"\n - - !GetAtt VPCEndpointSecurityGroup.GroupId\n VpcId: !Ref VpcId\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.ssm\"\n VpcEndpointType: Interface\n PrivateDnsEnabled: false\n SsmMessagesEndpoint:\n Type: AWS::EC2::VPCEndpoint\n Properties:\n PolicyDocument:\n Version: 2012-10-17\n Statement:\n - Effect: Allow\n Principal: \"*\"\n Action:\n - ssmmessages:*\n Resource:\n - \"*\"\n SubnetIds: !Ref SubnetIds\n SecurityGroupIds: !Split\n - \",\"\n - !Join\n - \",\"\n - - !GetAtt VPCEndpointSecurityGroup.GroupId\n VpcId: !Ref VpcId\n ServiceName: !Sub \"com.amazonaws.${AWS::Region}.ssmmessages\"\n VpcEndpointType: Interface\n PrivateDnsEnabled: false\n VPCEndpointEC2ICSecurityGroup:\n Type: AWS::EC2::SecurityGroup\n Properties:\n GroupDescription: VPC Endpoint Security Group for EC2 Instance Connect\n VpcId: !Ref VpcId\n VPCEndpointEC2ICSecurityGroupEgress:\n Type: AWS::EC2::SecurityGroupEgress\n Properties:\n GroupId: !Ref VPCEndpointEC2ICSecurityGroup\n FromPort: 22\n ToPort: 22\n IpProtocol: tcp\n DestinationSecurityGroupId: !Ref VPCEndpointEC2ICSecurityGroup\n VPCEndpointEC2ICSecurityGroupEgress2:\n Type: AWS::EC2::SecurityGroupEgress\n Properties:\n GroupId: !Ref VPCEndpointEC2ICSecurityGroup\n FromPort: 22\n ToPort: 22\n IpProtocol: tcp\n CidrIp: !
2If\n - HasVpcCIDR\n - !Ref VpcCIDR\n - \"0.0.0.0/0\"\n EC2InstanceConnectEndpoint:\n Type: AWS::EC2::InstanceConnectEndpoint\n UpdateReplacePolicy: \"Delete\"\n Properties:\n SubnetId: !Select\n - 0\n - !Ref SubnetIds\n SecurityGroupIds: !Split\n - \",\"\n - !Join\n - \",\"\n - - !GetAtt VPCEndpointEC2ICSecurityGroup.GroupId\n PreserveClientIp: false\n```\n"},{"frontmatter":{"title":"Generate a custom type-safe Helm Chart construct with cdk8s","description":"Use the new cdk8s import function to generate code for existing Kubernetes Helm Charts","image":"/images/cdk8sblog.png","date":"2023-11-02T06:00:00.000Z","draft":false},"slug":"cdk8s-helm-import","content":"\n\n\n\u003cdd\u003e By Andrzej Komarnicki \u003cbr /\u003e Cloud DevOps @ [smallfries.digital](https://smallfries.digital) \u003cbr /\u003e\nDate: 2023-11-02 \u003c/dd\u003e\n\nCloud Development Kit for Kubernetes (**cdk8s**) is a software development framework for defining Kubernetes applications and reusable abstractions using familiar programming languages and rich object-oriented APIs. cdk8s generates pure Kubernetes YAML - you can use cdk8s to define applications for any Kubernetes cluster running anywhere.\n\nRecently the cdk8s team has expanded the capabilities of the \"cdk8s import\" command, simplifying the process to incorporate Helm charts. Providing a Helm Chart URL to the \"cdk8s import\" command will auto-generate a custom type-safe construct for you that represents the Helm Chart in question.\n\nLet's start by initializing a new cdk8s project in an empty directory:\n\n```bash\nmkdir cdk8simporthelm \u0026\u0026 cd cdk8simporthelm\n```\n\n```bash\ncdk8s init typescript-app\n```\n\nYou should see the following basic structure of a cdk8s application with the _constructs_ and _cdk8s_ libraries:\n\n```typescript\nimport { Construct } from 'constructs';\nimport { App, Chart, ChartProps } from 'cdk8s';\n\nexport class MyChart extends Chart {\n constructor(scope: Construct, id: string, props: ChartProps = { }) {\n super(scope, id, props);\n\n // define resources here\n\n }\n}\n\nconst app = new App();\nnew MyChart(app, 'cdk8simporthelm');\napp.synth();\n});\n```\n\nNow let's go ahead and import the Bitnami Redis chart:\n\n```bash\ncdk8s import helm:https://charts.bitnami.com/bitnami/[email protected]\n```\n\nMoving back to our **main.ts** file we can now import the new Redis module that was generated in the previous step:\n\n```typescript\nimport { Construct } from \"constructs\";\nimport { App, Chart, ChartProps } from \"cdk8s\";\nimport { Redis, RedisArchitecture } from \"./imports/redis\";\n\nexport class MyChart extends Chart {\n constructor(scope: Construct, id: string, props: ChartProps = {}) {\n super(scope, id, props);\n\n new Redis(this, \"Redis\", {\n values: {\n architecture: RedisArchitecture.REPLICATION, // \u003c------- type safe property\n },\n });\n }\n}\n\nconst app = new App();\nnew MyChart(app, \"cdk8simporthelm\");\napp.synth();\n```\n\nAfter we're done adding anything else we need in addition to what's above, let's synthesize the k8s YAML manifest:\n\n```bash\ncdk8s synth\n```\n\nYou should now have your manifest generated in the **dist** subfolder and you can run kubectl apply as you would with any other manifest.\n\n\n\nYou can find the source code repo for this blog post here: https://github.com/smallfriesdigital/cdk8s-helm-import\n\nTo access more info about Values Schema, Additional Values, Globals and Dependencies visit https://cdk8s.io/docs/latest/cli/import/#helm-charts\n"},{"frontmatter":{"title":"Nested API routes with Node.js AWS Lambda Function URL, Middy HTTP router and AWS CDK","description":"Using AWS CDK with TypeScript to deploy and manage AWS Lambda functions that support nested HTTP routes served through function URL and CloudFront","image":"/images/blogpostmiddyroutescdk.png","date":"2023-04-17T05:00:00.000Z","draft":false},"slug":"apiroutes-nodejslambda-middy","content":"\n\n\n\u003cdd\u003e By Andrzej Komarnicki \u003cbr /\u003e Cloud DevOps @ [smallfries.digital](https://smallfries.digital) \u003cbr /\u003e\nDate: 2023-04-17 \u003c/dd\u003e\n\nWith the rising popularity of Function URLs, which are dedicated HTTP(S) endpoints for your Lambda function, we can avoid many of the complexities, costs and limitations associated with Amazon API Gateway. The downside is we also lose some of the nice features of an API Gateway that we get out of the box, one key feature being API routing based on method and path of an http event. This is where Middy, a Node.js middleware engine for AWS Lambda really comes in handy.\n\n
2In particular, we can achieve nested http routing within our Lambdas by leveraging Middy's [http-router](https://middy.js.org/docs/routers/http-router) handler. Let's start by cloning the starter project repo:\n\n```bash\ngit clone https://github.com/AndrzejKomarnicki/awscdk-nodejsfunction-middy\n```\n\nNext let's install the npm packages and run some AWS CDK CLI commands from the root working directory of our cloned repo.\n\n```bash\nnpm install\ncdk synth \u0026\u0026 cdk diff\n```\n\nBy default AWS CDK CLI will check your environment variables for your AWS creds and region first, if not found it will default back to your AWS credentials and config files. You can of course also specify the AWS CLI profile of choice. For more info on how to setup environments for your Stack instance head over to https://docs.aws.amazon.com/cdk/v2/guide/environments.html.\n\nAnyway, if everything checks out let's proceed to deploy the Stack as part of our CDK app:\n\n```bash\ncdk deploy\n```\n\nAfter deployment, you should see output similar to below:\n\n\n\nNext let's dive into the Lambda function source code before we test the endpoints...\u003cbr /\u003e\nOpen up **index.ts** from _/services/node-lambda/_\n\n```javascript\nimport middy from \"@middy/core\";\nimport jsonBodyParser from \"@middy/http-json-body-parser\";\nimport httpErrorHandler from \"@middy/http-error-handler\";\nimport httpSecurityHeaders from \"@middy/http-security-headers\";\nimport httpRouterHandler from \"@middy/http-router\";\nimport { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from \"aws-lambda\";\nimport { Logger, injectLambdaContext } from \"@aws-lambda-powertools/logger\";\n\nconst logger = new Logger({\n logLevel: \"INFO\",\n serviceName: \"middy-example-api\",\n});\n\nasync function getHandler(\n event: APIGatewayProxyEventV2,\n context: any\n): Promise\u003cAPIGatewayProxyResultV2\u003e {\n // the returned response will be checked against the type `APIGatewayProxyResultV2`\n logger.info(\"This is a INFO log with some context\");\n console.log(\"event ð\", event);\n return {\n statusCode: 200,\n body: JSON.stringify(`Hello from ${event.rawPath}`),\n };\n}\n\nasync function postHandler(\n event: APIGatewayProxyEventV2,\n context: any\n): Promise\u003cAPIGatewayProxyResultV2\u003e {\n // the returned response will be checked against the type `APIGatewayProxyResultV2`\n logger.info(\"This is a INFO log with some context\");\n console.log(\"event ð\", event);\n return {\n statusCode: 200,\n body: JSON.stringify(event.body),\n };\n}\n\n// routes served by httpRouterHandler middleware\n// you can add more nested handlers for routes (method and path) as needed\nconst routes = [\n {\n method: \"GET\",\n path: \"/user/{id}\",\n handler: getHandler,\n },\n {\n method: \"POST\",\n path: \"/user\",\n handler: postHandler,\n },\n];\n\nexport const handler = middy()\n .use(jsonBodyParser())\n .use(httpSecurityHeaders())\n .use(httpErrorHandler())\n .use(injectLambdaContext(logger)) // Change to (logger, { logEvent: true }) to log the incoming event\n .handler(httpRouterHandler(routes));\n```\n\nAs you can tell we import **httpRouterHandler** from **@middy/http-router**, and we define two async node.js functions - _getHandler()_ and _postHandler()_, which are referenced by the routes object as part of its handler property depending on http path and method in the http event.\n\nOf course you can re-organize these nested Lambda handlers to be exported as modules instead of having everything in one monolithic index.ts file, this only serves as a starting point for demonstration purposes.\n\nLet's proceed to test our endpoints with the different paths and methods we defined. You can go back to the outputs generated by \"CDK deploy\" and test against both the _Function URL_ and _CloudFront URL_.\n\n```json\nGET method on /user/{userid}\n```\n\n\n\n```json\nPOST method on /user\n```\n\n\n"}],"currentPage":1,"postIndex":{"frontmatter":{"title":"Latest posts","de
2scription":"latest blog posts for smallfries digital"},"content":"","mdxContent":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n return _jsx(_Fragment, {});\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}}},"mdxContent":{"compiledSource":"/*@jsxRuntime automatic @jsxImportSource react*/\nconst {Fragment: _Fragment, jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n return _jsx(_Fragment, {});\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = Object.assign({}, _provideComponents(), props.components);\n return MDXLayout ? _jsx(MDXLayout, Object.assign({}, props, {\n children: _jsx(_createMdxContent, props)\n })) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}}},"__N_SSG":true},"page":"/blogs","query":{},"buildId":"MheQSXCJqjkF0KAoqGagX","isFallback":false,"gsp":true,"scriptLoader":[]}</script>
2</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.