From 9d1e1fb72b87eac77141850c7e6dc4f7e26769fb Mon Sep 17 00:00:00 2001 From: DarthSim Date: Wed, 7 Feb 2024 19:13:50 +0600 Subject: [PATCH] Add AWS SAM template and assets --- .github/workflows/lint-and-upload.yml | 20 ++ .github/workflows/lint-template.yml | 19 ++ .github/workflows/release.yml | 49 ++++ .github/workflows/upload-template.yml | 26 ++ .gitignore | 1 + CHANGELOG.md | 5 + LICENSE | 21 ++ README.md | 53 ++++ assets/launch-stack.svg | 19 ++ assets/logo-dark.svg | 22 ++ assets/logo-light.svg | 31 ++ template.yml | 388 ++++++++++++++++++++++++++ 12 files changed, 654 insertions(+) create mode 100644 .github/workflows/lint-and-upload.yml create mode 100644 .github/workflows/lint-template.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/upload-template.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/launch-stack.svg create mode 100644 assets/logo-dark.svg create mode 100644 assets/logo-light.svg create mode 100644 template.yml diff --git a/.github/workflows/lint-and-upload.yml b/.github/workflows/lint-and-upload.yml new file mode 100644 index 0000000..4490a6d --- /dev/null +++ b/.github/workflows/lint-and-upload.yml @@ -0,0 +1,20 @@ +name: Lint and upload + +on: + push: + branches: ["**"] + pull_request: + +jobs: + lint-templates: + uses: ./.github/workflows/lint-template.yml + upload-template: + if: github.ref == 'refs/heads/master' + permissions: + contents: read + id-token: write + needs: [lint-templates] + uses: ./.github/workflows/upload-template.yml + with: + tag: latest + secrets: inherit diff --git a/.github/workflows/lint-template.yml b/.github/workflows/lint-template.yml new file mode 100644 index 0000000..02b78d2 --- /dev/null +++ b/.github/workflows/lint-template.yml @@ -0,0 +1,19 @@ +name: Lint templates + +on: + workflow_call: + +jobs: + build-and-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + - name: Install dev dependencies + run: pip install cfn-lint + - name: Lint templates + run: cfn-lint template.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..553c3ec --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + +jobs: + lint-template: + uses: ./.github/workflows/lint-template.yml + upload-template: + permissions: + contents: read + id-token: write + needs: [lint-template] + uses: ./.github/workflows/upload-template.yml + with: + tag: ${{ github.ref_name }} + secrets: inherit + release: + needs: [lint-template, upload-template] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Prepare notes + id: prepare-notes + run: | + # Extract changelog entries between this and previous version headers + escaped_version=$(echo ${GITHUB_REF_NAME#v} | sed -e 's/[]\/$*.^[]/\\&/g') + awk "BEGIN{inrelease=0} /## \[${escaped_version}\]/{inrelease=1;next} /## \[[0-9]+\.[0-9]+\.[0-9]+.*\]/{inrelease=0;exit} {if (inrelease) print}" CHANGELOG.md \ + > RELEASE_NOTES.txt + + echo "" >> RELEASE_NOTES.txt + echo "[![](assets/launch-stack.svg)](https://console.aws.amazon.com/cloudformation/home#/stacks/new?stackName=imgproxy&templateURL=https://imgproxy-cf.s3.amazonaws.com/sam/${{ github.ref_name }}/template.yml)" >> RELEASE_NOTES.txt + + # Write prerelease="true" env if tag name has any suffix after vMAJOR.MINOR.PATCH + if [[ ${GITHUB_REF_NAME} =~ ^v[0-9]+\.[0-9]+\.[0-9]+.+ ]]; then + echo 'prerelease="true"' >> $GITHUB_OUTPUT + else + echo 'prerelease="false"' >> $GITHUB_OUTPUT + fi + - name: Release + uses: softprops/action-gh-release@v1 + with: + body_path: RELEASE_NOTES.txt + prerelease: ${{ fromJSON(steps.prepare-notes.outputs.prerelease) }} diff --git a/.github/workflows/upload-template.yml b/.github/workflows/upload-template.yml new file mode 100644 index 0000000..820d0d6 --- /dev/null +++ b/.github/workflows/upload-template.yml @@ -0,0 +1,26 @@ +name: Upload Templates to S3 + +on: + workflow_call: + inputs: + tag: + required: true + type: string + +jobs: + upload: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + audience: sts.amazonaws.com + aws-region: us-east-1 + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + - name: Upload template + run: aws s3 cp template.yml s3://imgproxy-cf/sam/${{ inputs.tag }}/template.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6c3997b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [0.1.0] - 2024-02-07 +### Added +- AWS SAM / CloudFormation template for deploying imgproxy to AWS Lambda diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..28b3708 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sergei Aleksandrovich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..200058e --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +

+ + + + + imgproxy logo + + +

+ +

+ Website | + Blog | + Documentation | + imgproxy Pro | + Docker | + Twitter | + Discord +

+ +

+GH Lint +

+ +--- + +[imgproxy](https://imgproxy.net) is a fast and secure standalone server for resizing and converting remote images. The main principles of imgproxy are simplicity, speed, and security. + +This repository contains an [AWS Serverless Application Model (SAM)](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) template compatible with [AWS CloudFormation](https://aws.amazon.com/cloudformation/) that allows you to deploy imgproxy to AWS Lambda. + +## Using the template + +We uploaded the template to our S3 bucket so you can use by a click of a button. The links below will take you to the AWS CloudFormation console with the template pre-filled. You just need to provide the required parameters and click **Create stack**. + +[![](assets/launch-stack.svg)](https://console.aws.amazon.com/cloudformation/home#/stacks/new?stackName=imgproxy&templateURL=https://imgproxy-cf.s3.amazonaws.com/sam/latest/template.yml) + +> [!NOTE] +> The link in the README points to the template from the `master` branch. If you want to use a specific version, you can find the links in the [releases](https://github.com/imgproxy/imgproxy-aws-sam/releases) section. + +> [!WARNING] +> Official Docker images of imgproxy prior to version `3.22.0` do not contain [AWS Lambda adapter](https://github.com/awslabs/aws-lambda-web-adapter). +> +> If you want to use the template with an older version of imgproxy, you need to build a custom Docker image with the adapter. Take note that this template assumes that the AWS Lambda adapter is configed to work in the `rsponse_stream` mode. + +## License + +imgproxy-aws-sam is licensed under the MIT license. + +See [LICENSE](https://github.com/imgproxy/imgproxy-aws-sam/blob/master/LICENSE) for the full license text. + +## Security Contact + +To report a security vulnerability, please contact us at security@imgproxy.net. We will coordinate the fix and disclosure. diff --git a/assets/launch-stack.svg b/assets/launch-stack.svg new file mode 100644 index 0000000..bd6fe10 --- /dev/null +++ b/assets/launch-stack.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/logo-dark.svg b/assets/logo-dark.svg new file mode 100644 index 0000000..165e945 --- /dev/null +++ b/assets/logo-dark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/logo-light.svg b/assets/logo-light.svg new file mode 100644 index 0000000..c4bded8 --- /dev/null +++ b/assets/logo-light.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/template.yml b/template.yml new file mode 100644 index 0000000..3b3859b --- /dev/null +++ b/template.yml @@ -0,0 +1,388 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Description: Imgproxy running on AWS Lambda + +Parameters: + FunctionName: + Type: String + Description: A name for the function. If you don't specify a name, stack name is used. + ImageUri: + Type: String + Description: >- + The URI of the Docker image to use for the function. + The image must be in an Amazon Elastic Container Registry (Amazon ECR) repository. + MemorySize: + Type: Number + Description: >- + Amount of memory in megabytes to give to the function. + AWS Lambda will allocate CPU power linearly in proportion to the amount of memory configured, + so it's recommended to allocate at least 2048 MB of memory to the function. + Default: 2048 + MinValue: 1024 + MaxValue: 10240 + Timeout: + Type: Number + Description: The amount of time in seconds that Lambda allows a function to run before stopping it. + Default: 60 + MinValue: 1 + MaxValue: 60 + EnvironmentSecretARN: + Type: String + Description: >- + ARN of an AWS Secrets Manager secret containing environment variables. + See https://docs.imgproxy.net/latest/configuration/loading_environment_variables#environment-file-syntax for the secret syntax. + See https://docs.imgproxy.net/configuration for supported environment variables + Default: '' + EnvironmentSecretVersionID: + Type: String + Description: >- + Version ID of the AWS Secrets Manager secret containing environment variables. + If not set, the latest version is used + Default: '' + EnvironmentSystemsManagerParametersPath: + Type: String + Description: >- + A path of AWS Systems Manager Parameter Store parameters containing the environment variables. + The path should start with a slash (/) but should not have a slash (/) at the end. + See https://docs.imgproxy.net/latest/configuration/loading_environment_variables#aws-systems-manager-path + to learn how imgproxy maps AWS Systems Manager Parameter Store parameters to environment variables. + See https://docs.imgproxy.net/configuration for supported environment variables + Default: '' + S3Objects: + Type: CommaDelimitedList + Description: >- + ARNs of S3 objects (comma delimited) that imgproxy should have access to. + You can grant access to multiple objects with a single ARN by using wildcards. + Example: arn:aws:s3:::my-images-bucket/*,arn:aws:s3:::my-assets-bucket/images/* + Default: '' + S3AssumeRoleARN: + Type: String + Description: >- + ARN of IAM Role that S3 client should assume. + This allows you to provide imgproxy access to third-party S3 buckets that the assummed IAM Role has access to + Default: '' + S3MultiRegion: + Type: String + Description: >- + Should imgproxy be able to access S3 buckets in other regions? + By default, imgproxy can access only S3 buckets located in the same region as imgproxy + Default: 'No' + AllowedValues: + - 'Yes' + - 'No' + S3ClientSideDecryption: + Type: String + Description: >- + Should imgproxy use S3 decryption client? + The decription client will be used forall objects in all S3 buckets, so unecrypted objects won't be accessable + Default: 'No' + AllowedValues: + - 'Yes' + - 'No' + PathPrefix: + Type: String + Description: Path prefix, beginning with a slash (/).Do not add a slash (/) at the end of the path + Default: '' + CreateCloudFrontDistribution: + Type: String + Description: >- + Should caching CloudFront distribution be created? + Default: 'Yes' + AllowedValues: + - 'Yes' + - 'No' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Lambda Function + Parameters: + - FunctionName + - ImageUri + - MemorySize + - Timeout + - Label: + default: Load environment from an AWS Secrets Manager secret + Parameters: + - EnvironmentSecretARN + - EnvironmentSecretVersionID + - Label: + default: Load environment from AWS Systems Manager Parameter Store + Parameters: + - EnvironmentSystemsManagerParametersPath + - Label: + default: S3 integration + Parameters: + - S3Objects + - S3AssumeRoleARN + - S3MultiRegion + - S3ClientSideDecryption + - Label: + default: Endpoint + Parameters: + - PathPrefix + - CreateCloudFrontDistribution + ParameterLabels: + FunctionName: + default: Function name + ImageUri: + default: Docker image + MemorySize: + default: Memory size + Timeout: + default: Timeout + EnvironmentSecretARN: + default: Secrets Manager secret ARN (optional) + EnvironmentSecretVersionID: + default: Secrets Manager secret version ID (optional) + EnvironmentSystemsManagerParametersPath: + default: Systems Manager Parameter Store parameters path (optional) + S3Objects: + default: S3 objects (optional) + S3AssumeRoleARN: + default: IAM Role ARN to assume (optional) + S3MultiRegion: + default: Enable multi-region mode + S3ClientSideDecryption: + default: Enable client-side decryption + PathPrefix: + default: Path prefix (optional) + CreateCloudFrontDistribution: + default: Create CloudForont distribution? + +Conditions: + HaveFunctionName: !Not + - !Equals + - !Ref 'FunctionName' + - '' + HaveEnvironmentSecretArn: !Not + - !Equals + - !Ref 'EnvironmentSecretARN' + - '' + HaveEnvironmentSystemsManagerParametersPath: !Not + - !Equals + - !Ref 'EnvironmentSystemsManagerParametersPath' + - '' + HaveS3Objects: !Not + - !Equals + - !Join + - '' + - !Ref 'S3Objects' + - '' + HaveS3AssumeRole: !Not + - !Equals + - !Ref 'S3AssumeRoleARN' + - '' + EnableS3MultiRegion: !Equals + - !Ref 'S3MultiRegion' + - 'Yes' + EnableS3ClientSideDecryption: !Equals + - !Ref 'S3ClientSideDecryption' + - 'Yes' + HavePathPrefix: !Not + - !Equals + - !Ref 'PathPrefix' + - '' + DeployCloudFront: !Equals + - !Ref 'CreateCloudFrontDistribution' + - 'Yes' + +Resources: + ImgproxyFunction: + Type: AWS::Serverless::Function + Properties: + Architectures: [arm64] + FunctionName: !If + - HaveFunctionName + - !Ref 'FunctionName' + - !Ref 'AWS::StackName' + PackageType: Image + ImageUri: !Ref ImageUri + MemorySize: !Ref MemorySize + Timeout: !Ref Timeout + Environment: + Variables: + PORT: '8080' + IMGPROXY_LOG_FORMAT: json + IMGPROXY_ENV_AWS_SECRET_ID: !If + - HaveEnvironmentSecretArn + - !Ref 'EnvironmentSecretARN' + - !Ref 'AWS::NoValue' + IMGPROXY_ENV_AWS_SECRET_VERSION_ID: !If + - HaveEnvironmentSecretArn + - !Ref 'EnvironmentSecretVersionID' + - !Ref 'AWS::NoValue' + IMGPROXY_ENV_AWS_SSM_PARAMETERS_PATH: !If + - HaveEnvironmentSystemsManagerParametersPath + - !Ref 'EnvironmentSystemsManagerParametersPath' + - !Ref 'AWS::NoValue' + IMGPROXY_USE_S3: '1' + IMGPROXY_S3_ASSUME_ROLE_ARN: !If + - HaveS3AssumeRole + - !Ref 'S3AssumeRoleARN' + - !Ref 'AWS::NoValue' + IMGPROXY_S3_MULTI_REGION: !If + - EnableS3MultiRegion + - '1' + - !Ref 'AWS::NoValue' + IMGPROXY_S3_USE_DECRYPTION_CLIENT: !If + - EnableS3ClientSideDecryption + - '1' + - !Ref 'AWS::NoValue' + IMGPROXY_PATH_PREFIX: !If + - HavePathPrefix + - !Ref 'PathPrefix' + - !Ref 'AWS::NoValue' + IMGPROXY_CLOUD_WATCH_SERVICE_NAME: !If + - HaveFunctionName + - !Ref 'FunctionName' + - !Ref 'AWS::StackName' + IMGPROXY_CLOUD_WATCH_NAMESPACE: imgproxy + IMGPROXY_CLOUD_WATCH_REGION: !Ref 'AWS::Region' + + FunctionUrlConfig: + AuthType: NONE + InvokeMode: RESPONSE_STREAM + LoggingConfig: + LogFormat: JSON + Policies: + - Statement: + - Sid: CloudWatch + Effect: Allow + Action: + - cloudwatch:PutMetricData + - cloudwatch:PutMetricStream + Resource: + - '*' + - Sid: AwsMarketplace + Effect: Allow + Action: + - aws-marketplace:MeterUsage + Resource: + - '*' + - !If + - HaveEnvironmentSecretArn + - Sid: SecretsManagerAccess + Effect: Allow + Action: + - secretsmanager:GetSecretValue + - secretsmanager:ListSecretVersionIds + Resource: + - !Ref 'EnvironmentSecretARN' + - !Ref 'AWS::NoValue' + - !If + - HaveEnvironmentSystemsManagerParametersPath + - Sid: SystemsManagerAccess + Effect: Allow + Action: + - ssm:GetParametersByPath + Resource: + - !Join + - '' + - - 'arn:aws:ssm:' + - !Ref 'AWS::Region' + - ':' + - !Ref 'AWS::AccountId' + - :parameter + - !Ref 'EnvironmentSystemsManagerParametersPath' + - !Ref 'AWS::NoValue' + - !If + - HaveS3Objects + - Sid: S3Access + Effect: Allow + Action: + - s3:GetObject + - s3:GetObjectVersion + Resource: !Ref 'S3Objects' + - !Ref 'AWS::NoValue' + - !If + - HaveS3AssumeRole + - Sid: IAMRoleAssume + Effect: Allow + Action: + - sts:AssumeRole + Resource: !Ref 'S3AssumeRoleARN' + - !Ref 'AWS::NoValue' + - !If + - EnableS3ClientSideDecryption + - Sid: KMSDecrypt + Effect: Allow + Action: + - kms:Decrypt + Resource: !Join + - ':' + - - arn:aws:kms:* + - !Ref 'AWS::AccountId' + - key/* + - !Ref 'AWS::NoValue' + Tracing: Active + + CloudFrontCachePolicy: + Type: AWS::CloudFront::CachePolicy + Condition: DeployCloudFront + Properties: + CachePolicyConfig: + Name: !Join + - '-' + - - !Ref 'AWS::StackName' + - cache-policy + DefaultTTL: 31536000 + MaxTTL: 31536000 + MinTTL: 1 + ParametersInCacheKeyAndForwardedToOrigin: + CookiesConfig: + CookieBehavior: none + EnableAcceptEncodingBrotli: false + EnableAcceptEncodingGzip: false + HeadersConfig: + HeaderBehavior: whitelist + Headers: + - Accept + QueryStringsConfig: + QueryStringBehavior: none + + CloudFrontDistribution: + Type: AWS::CloudFront::Distribution + Condition: DeployCloudFront + Properties: + DistributionConfig: + Enabled: true + Origins: + - DomainName: !Select + - 2 + - !Split ['/', !GetAtt 'ImgproxyFunctionUrl.FunctionUrl'] + Id: !Join + - '-' + - - !Ref 'AWS::StackName' + - origin + CustomOriginConfig: + OriginProtocolPolicy: https-only + OriginReadTimeout: !Ref 'Timeout' + OriginPath: !Ref 'PathPrefix' + OriginShield: + Enabled: true + OriginShieldRegion: !Ref 'AWS::Region' + DefaultCacheBehavior: + TargetOriginId: !Join + - '-' + - - !Ref 'AWS::StackName' + - origin + CachePolicyId: !Ref 'CloudFrontCachePolicy' + ViewerProtocolPolicy: redirect-to-https + PriceClass: PriceClass_All + ViewerCertificate: + CloudFrontDefaultCertificate: true + +Outputs: + FunctionArn: + Description: The ARN of the function + Value: !GetAtt 'ImgproxyFunction.Arn' + DirectURL: + Description: The direct URL endpoint for imgproxy + Value: !GetAtt 'ImgproxyFunctionUrl.FunctionUrl' + CloudFrontURL: + Description: The CloudFront endpoint for imgproxy + Value: !GetAtt 'CloudFrontDistribution.DomainName' + Condition: DeployCloudFront