AWS Cloud Operations Blog

Secure Serverless Development Using AWS Service Catalog

Serverless computing allows you to build and run applications and services without having to manage servers. AWS Service Catalog allows you to create and manage catalogs of services that are approved for use on AWS. Combining Serverless and Service Catalog together is a great way to safely allow developers to create products and services in the cloud.

In this post, I demonstrate how to combine the controls of Service Catalog with AWS Lambda and Amazon API Gateway and allow your developers to build a Serverless application without full AWS access.

 

Workflow

For this example, I have written a Lambda function that returns sample JSON and uploaded it to Amazon S3. I then created a product in Service Catalog that leverages an AWS CloudFormation template to deploy a Serverless product calling the Lambda function and API Gateway.

Initial setup

There are a few basic actions to take before allowing developers to build their Serverless application. The following steps require administrator access to AWS resources via the AWS Management Console.

  1. Choose the AWS Region in which to host this Serverless application.
  2. Create an S3 bucket to host your Lambda code package.
  3. Use your favorite editor and create a sample Python Serverless Application serverlessapp.py with the following contents:
    # A Python Lambda Script That Returns Some JSON
    def endpoint(event, context):
        output = { "data": {"a": 1, "b": 2, "c": 3} }
        return output
  4. Compress/ZIP this file with the name serverlessapp.zip you may have to rename the zip after compressing it, but ensure the file that gets uncompressed has the serverlessapp.py name.
  5. Upload this file into the S3 bucket you created with public read permissions on the object
  6. Create an AWS service role for Lambda so that your application can inherit and execute against AWS resources.
  7. Add the following managed policies to your role:
    • AWSLambdaFullAccess
    • AmazonAPIGatewayAdministrator
    • AWSServiceCatalogEndUserFullAccess
  8. Name your role scserverless.
  9. After your role is created, open the role and choose Trust Relationships, edit trust relationship. Copy and paste over the existing text the following text:
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": [
              "lambda.amazonaws.com",
              "servicecatalog.amazonaws.com",
              "apigateway.amazonaws.com"
            ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
  10. Create an AWS IAM user for the developer creating the Serverless application, with AWS Management Console Access and attach the following managed permissions:
    • AWSServiceCatalogAdminFullAccess
    • AmazonS3FullAccess
    • ReadOnlyAccess

Service Catalog and Serverless application setup

Service Catalog acts as the landing zone for your developers to access building and launching Serverless applications. They only have access to build and launch things from Service Catalog itself. Products created in Service Catalog are all based on CloudFormation templates.

  1. Log out of the Management Console as your admin user and log back in as the developer user you created in the previous step.
  2. Create a Service Catalog portfolio for the developer or developers who should have access to build Serverless applications.
  3. Add the developer user that you created to the portfolio.
  4. Use your favorite editor and create a AWS Service Catalog product serverlessapp.yaml with the following contents:
    AWSTemplateFormatVersion: "2010-09-09"
    Description: Python Serverless Application that outputs some sample JSON
    Metadata:
      'AWS::CloudFormation::Interface':
        ParameterGroups:
          - Label:
              default: Runtime and Environment
            Parameters:
              - Runtime
              - MemorySize
              - Timeout
              - Role
          - Label:
              default: S3 Info
            Parameters:
              - S3Bucket
              - S3Key
        ParameterLabels:
          Runtime:
            default: 'Runtime:'
          MemorySize:
            default: 'Memory Size:'
          Timeout:
            default: 'Execution Timeout:'
          S3Bucket:
            default: 'Bucket:'
          S3Key:
            default: 'Key:'
    Parameters:
      Owner:
        Description: Who owns this Serverless Application? Enter email here.
        Type: String
        Default: developer@example.com
        AllowedPattern: "^[\\x20-\\x45]?[\\w-\\+]+(\\.[\\w]+)*@[\\w-]+(\\.[\\w]+)*(\\.[a-z]{2,})$"
        ConstraintDescription: must be a valid email address
      Runtime:
        Description: Select the correct runtime for the lambda
        Default: python2.7
        AllowedValues:
          - nodejs6.10
          - java8
          - python2.7
          - python3.6
        Type: String
      S3Bucket:
        Description: The bucket the code is in
        Type: String
      S3Key:
        Description: The name of the zip file of your code
        Type: String
      Description:
        Description: The description of the Lambda Function
        Type: String
        Default: Serverless Example
      MemorySize:
        Description: 'Memory Size:'
        Type: Number
        Default: 128
        AllowedValues:
          - 128
          - 256
          - 512
          - 1024
      Timeout:
        Description: Execution Timeout
        Type: Number
        Default: 30
    Resources:
      endpointApiPermissionProd:
        Type: 'AWS::Lambda::Permission'
        Properties:
          Action: 'lambda:invokeFunction'
          Principal: apigateway.amazonaws.com
          FunctionName: !Ref endpoint
          SourceArn: !Sub
            - >-
              arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/endpoint_python
            - __Stage__: Prod
              __ApiId__: !Ref ServerlessRestApi
      ServerlessRestApiProdStage:
        Type: 'AWS::ApiGateway::Stage'
        Properties:
          DeploymentId: !Ref ServerlessRestApiDeployment00
          RestApiId: !Ref ServerlessRestApi
          StageName: Prod
      ServerlessRestApiDeployment00:
        Type: 'AWS::ApiGateway::Deployment'
        Properties:
          RestApiId: !Ref ServerlessRestApi
          Description: 'RestApi deployment id: 00'
          StageName: Stage
      endpoint:
        Type: 'AWS::Lambda::Function'
        Properties:
          Code:
            S3Bucket: !Ref S3Bucket
            S3Key: !Ref S3Key
          Description: ''
          MemorySize: !Ref MemorySize
          Handler: serverlessapp.endpoint
          Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/scserverless'
          Timeout: !Ref Timeout
          Runtime: !Ref Runtime
      ServerlessRestApi:
        Type: 'AWS::ApiGateway::RestApi'
        Properties:
          Body:
            info:
              version: '1.0'
              title: !Ref 'AWS::StackName'
            paths:
              /endpoint_python:
                get:
                  consumes:
                    - application/json
                  produces:
                    - application/json
                  responses:
                    '200':
                      description: 200 response
                      headers:
                        Content-Type:
                          type: string
                  x-amazon-apigateway-integration:
                    httpMethod: POST
                    credentials: !Sub 'arn:aws:iam::${AWS::AccountId}:role/scserverless'
                    type: aws
                    uri: !Sub >-
                      arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${endpoint.Arn}/invocations
                    responses: {
                      "default": {
                        "statusCode": "200",
                        "responseTemplates": {
                          application/json: ""
                        }
                      }
                    }
                    passthroughBehavior: "when_no_templates"
            swagger: '2.0'
      endpointApiPermissionTest:
        Type: 'AWS::Lambda::Permission'
        Properties:
          Action: 'lambda:invokeFunction'
          Principal: apigateway.amazonaws.com
          FunctionName: !Ref endpoint
          SourceArn: !Sub
            - >-
              arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/endpoint_python
            - __Stage__: '*'
              __ApiId__: !Ref ServerlessRestApi
    Outputs:
     TestURL:
      Description: The serverless url you can test in a browser.
      Value: !Join [ "", [ "https://", !Ref "ServerlessRestApi", ".execute-api.", !Sub "${AWS::Region}", ".amazonaws.com/Prod/endpoint_python"]]
     FunctionName:
      Description: The name of the lambda function.
      Value: !Ref endpointApiPermissionProd
     APIGatewayName:
      Description: The name of associated API Gateway.
      Value: !Ref 'AWS::StackName'
     S3Bucket:
      Description: The S3 bucket your serverless app resides.
      Value: !Ref S3Bucket
     ServerlessAppZIP:
      Description: The serverless app zip file name.
      Value: !Ref S3Key
  5. Go into your portfolio and choose Upload new product.
    • Product Name: Python Serverless Application
    • Description: Outputs some sample JSON
    • Provided by: [your developer user]
  6. Choose Next, Next.
  7. For Select template, choose Upload a template file.
    • Click Choose File and select the serverlessapp.yaml file you created
    • Version title: Serverless version 1
  8. Choose Next, Create.

Create Service Catalog constraints

Applying a Service Catalog launch constraint is the way to control developer access to Lambda functions, API Gateway API actions, and other resources for Serverless applications through a IAM role and not through individual user management console access.

  1. In your Service Catalog portfolio, expand Constraints.
  2. Choose Add constraints.
    • Product: Python Serverless Application
    • Constraint Type: Launch
  3. Choose Continue.
    • IAM Role: scserverless
  4. Choose Submit.

Service Catalog Serverless application setup

With CloudFormation templates and Service Catalog, you can build a Lambda template that allows you to launch and configure the specific resources that your Serverless application needs. The output of the CloudFormation template provides you with the API Gateway entry point to your Lambda application.

    1. In the Service Catalog console, choose Product List.
    2. Choose the Python Serverless Application option and choose Launch product.
      • Name: Python-Serverless-Application
      • Version: Serverless Version 1
    3. Choose Next.
    4. For Parameters, enter the following:
      • Runtime: Python 2.7
      • Memory Size: 128
      • Execution Timeout: 30
      • Bucket: {The bucket name you created that has the Python zip file in this example, such as serverless-bucket}
      • Key: {The name of your Python zip file in this example serverlessapp.zip}
      • Owner: {your email}
      • Description: {Serverless Example}

    • Leave the rest of the fields as their default values, and choose Launch.
    • Under Events, choose the refresh icon until you see Status Succeeded
    • Expand Outputs and copy the unique value for TestURL, for example, https://outbd7gim9.execute-api.us-west-2.amazonaws.com/Prod/endpoint_python
    • Paste the URL into a browser and you should get the following output:
      {"data": {"a": 1, "c": 3, "b": 2}}

Summary

In this post, I’ve demonstrated how to use Service Catalog and allow your developers to create Serverless applications safely without giving them access to the AWS Management Console. Service Catalog can also allow you to give limited granular access for deploying S3, Amazon EC2, and other AWS services in the same manner, making it a powerful control mechanism.

 

About the Author

 

 

 

 

 

Phil Chen has been designing and deploying AWS architectures for over 10 years and loves helping customers do the same.