github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_lambda_permission.go (about)

     1  package aws
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/lambda"
    14  	"github.com/hashicorp/terraform/helper/resource"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  var LambdaFunctionRegexp = `^(arn:[\w-]+:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)(:(\$LATEST|[a-zA-Z0-9-_]+))?$`
    19  
    20  func resourceAwsLambdaPermission() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsLambdaPermissionCreate,
    23  		Read:   resourceAwsLambdaPermissionRead,
    24  		Delete: resourceAwsLambdaPermissionDelete,
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"action": {
    28  				Type:         schema.TypeString,
    29  				Required:     true,
    30  				ForceNew:     true,
    31  				ValidateFunc: validateLambdaPermissionAction,
    32  			},
    33  			"function_name": {
    34  				Type:         schema.TypeString,
    35  				Required:     true,
    36  				ForceNew:     true,
    37  				ValidateFunc: validateLambdaFunctionName,
    38  			},
    39  			"principal": {
    40  				Type:     schema.TypeString,
    41  				Required: true,
    42  				ForceNew: true,
    43  			},
    44  			"qualifier": {
    45  				Type:         schema.TypeString,
    46  				Optional:     true,
    47  				ForceNew:     true,
    48  				ValidateFunc: validateLambdaQualifier,
    49  			},
    50  			"source_account": {
    51  				Type:         schema.TypeString,
    52  				Optional:     true,
    53  				ForceNew:     true,
    54  				ValidateFunc: validateAwsAccountId,
    55  			},
    56  			"source_arn": {
    57  				Type:         schema.TypeString,
    58  				Optional:     true,
    59  				ForceNew:     true,
    60  				ValidateFunc: validateArn,
    61  			},
    62  			"statement_id": {
    63  				Type:         schema.TypeString,
    64  				Required:     true,
    65  				ForceNew:     true,
    66  				ValidateFunc: validatePolicyStatementId,
    67  			},
    68  		},
    69  	}
    70  }
    71  
    72  func resourceAwsLambdaPermissionCreate(d *schema.ResourceData, meta interface{}) error {
    73  	conn := meta.(*AWSClient).lambdaconn
    74  
    75  	functionName := d.Get("function_name").(string)
    76  
    77  	// There is a bug in the API (reported and acknowledged by AWS)
    78  	// which causes some permissions to be ignored when API calls are sent in parallel
    79  	// We work around this bug via mutex
    80  	awsMutexKV.Lock(functionName)
    81  	defer awsMutexKV.Unlock(functionName)
    82  
    83  	input := lambda.AddPermissionInput{
    84  		Action:       aws.String(d.Get("action").(string)),
    85  		FunctionName: aws.String(functionName),
    86  		Principal:    aws.String(d.Get("principal").(string)),
    87  		StatementId:  aws.String(d.Get("statement_id").(string)),
    88  	}
    89  
    90  	if v, ok := d.GetOk("qualifier"); ok {
    91  		input.Qualifier = aws.String(v.(string))
    92  	}
    93  	if v, ok := d.GetOk("source_account"); ok {
    94  		input.SourceAccount = aws.String(v.(string))
    95  	}
    96  	if v, ok := d.GetOk("source_arn"); ok {
    97  		input.SourceArn = aws.String(v.(string))
    98  	}
    99  
   100  	log.Printf("[DEBUG] Adding new Lambda permission: %s", input)
   101  	var out *lambda.AddPermissionOutput
   102  	err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   103  		var err error
   104  		out, err = conn.AddPermission(&input)
   105  
   106  		if err != nil {
   107  			if awsErr, ok := err.(awserr.Error); ok {
   108  				// IAM is eventually consistent :/
   109  				if awsErr.Code() == "ResourceConflictException" {
   110  					return resource.RetryableError(
   111  						fmt.Errorf("[WARN] Error adding new Lambda Permission for %s, retrying: %s",
   112  							*input.FunctionName, err))
   113  				}
   114  			}
   115  			return resource.NonRetryableError(err)
   116  		}
   117  		return nil
   118  	})
   119  
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	if out != nil && out.Statement != nil {
   125  		log.Printf("[DEBUG] Created new Lambda permission: %s", *out.Statement)
   126  	} else {
   127  		log.Printf("[DEBUG] Created new Lambda permission, but no Statement was included")
   128  	}
   129  
   130  	d.SetId(d.Get("statement_id").(string))
   131  
   132  	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
   133  		// IAM is eventually cosistent :/
   134  		err := resourceAwsLambdaPermissionRead(d, meta)
   135  		if err != nil {
   136  			if strings.HasPrefix(err.Error(), "Error reading Lambda policy: ResourceNotFoundException") {
   137  				return resource.RetryableError(
   138  					fmt.Errorf("[WARN] Error reading newly created Lambda Permission for %s, retrying: %s",
   139  						*input.FunctionName, err))
   140  			}
   141  			if strings.HasPrefix(err.Error(), "Failed to find statement \""+d.Id()) {
   142  				return resource.RetryableError(
   143  					fmt.Errorf("[WARN] Error reading newly created Lambda Permission statement for %s, retrying: %s",
   144  						*input.FunctionName, err))
   145  			}
   146  
   147  			log.Printf("[ERROR] An actual error occurred when expecting Lambda policy to be there: %s", err)
   148  			return resource.NonRetryableError(err)
   149  		}
   150  		return nil
   151  	})
   152  
   153  	return err
   154  }
   155  
   156  func resourceAwsLambdaPermissionRead(d *schema.ResourceData, meta interface{}) error {
   157  	conn := meta.(*AWSClient).lambdaconn
   158  
   159  	input := lambda.GetPolicyInput{
   160  		FunctionName: aws.String(d.Get("function_name").(string)),
   161  	}
   162  	if v, ok := d.GetOk("qualifier"); ok {
   163  		input.Qualifier = aws.String(v.(string))
   164  	}
   165  
   166  	log.Printf("[DEBUG] Looking for Lambda permission: %s", input)
   167  	var out *lambda.GetPolicyOutput
   168  	var statement *LambdaPolicyStatement
   169  	err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   170  		// IAM is eventually cosistent :/
   171  		var err error
   172  		out, err = conn.GetPolicy(&input)
   173  		if err != nil {
   174  			if awsErr, ok := err.(awserr.Error); ok {
   175  				if awsErr.Code() == "ResourceNotFoundException" {
   176  					return resource.RetryableError(err)
   177  				}
   178  			}
   179  			return resource.NonRetryableError(err)
   180  		}
   181  
   182  		policyInBytes := []byte(*out.Policy)
   183  		policy := LambdaPolicy{}
   184  		err = json.Unmarshal(policyInBytes, &policy)
   185  		if err != nil {
   186  			return resource.NonRetryableError(err)
   187  		}
   188  
   189  		statement, err = findLambdaPolicyStatementById(&policy, d.Id())
   190  		return resource.RetryableError(err)
   191  	})
   192  
   193  	if err != nil {
   194  		// Missing whole policy or Lambda function (API error)
   195  		if awsErr, ok := err.(awserr.Error); ok {
   196  			if awsErr.Code() == "ResourceNotFoundException" {
   197  				log.Printf("[WARN] No Lambda Permission Policy found: %v", input)
   198  				d.SetId("")
   199  				return nil
   200  			}
   201  		}
   202  
   203  		// Missing permission inside valid policy
   204  		if nfErr, ok := err.(*resource.NotFoundError); ok {
   205  			log.Printf("[WARN] %s", nfErr)
   206  			d.SetId("")
   207  			return nil
   208  		}
   209  
   210  		return err
   211  	}
   212  
   213  	qualifier, err := getQualifierFromLambdaAliasOrVersionArn(statement.Resource)
   214  	if err != nil {
   215  		log.Printf("[ERR] Error getting Lambda Qualifier: %s", err)
   216  	}
   217  	d.Set("qualifier", qualifier)
   218  
   219  	// Save Lambda function name in the same format
   220  	if strings.HasPrefix(d.Get("function_name").(string), "arn:"+meta.(*AWSClient).partition+":lambda:") {
   221  		// Strip qualifier off
   222  		trimmedArn := strings.TrimSuffix(statement.Resource, ":"+qualifier)
   223  		d.Set("function_name", trimmedArn)
   224  	} else {
   225  		functionName, err := getFunctionNameFromLambdaArn(statement.Resource)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		d.Set("function_name", functionName)
   230  	}
   231  
   232  	d.Set("action", statement.Action)
   233  	// Check if the pricipal is a cross-account IAM role
   234  	if _, ok := statement.Principal["AWS"]; ok {
   235  		d.Set("principal", statement.Principal["AWS"])
   236  	} else {
   237  		d.Set("principal", statement.Principal["Service"])
   238  	}
   239  
   240  	if stringEquals, ok := statement.Condition["StringEquals"]; ok {
   241  		d.Set("source_account", stringEquals["AWS:SourceAccount"])
   242  	}
   243  
   244  	if arnLike, ok := statement.Condition["ArnLike"]; ok {
   245  		d.Set("source_arn", arnLike["AWS:SourceArn"])
   246  	}
   247  
   248  	return nil
   249  }
   250  
   251  func resourceAwsLambdaPermissionDelete(d *schema.ResourceData, meta interface{}) error {
   252  	conn := meta.(*AWSClient).lambdaconn
   253  
   254  	functionName := d.Get("function_name").(string)
   255  
   256  	// There is a bug in the API (reported and acknowledged by AWS)
   257  	// which causes some permissions to be ignored when API calls are sent in parallel
   258  	// We work around this bug via mutex
   259  	awsMutexKV.Lock(functionName)
   260  	defer awsMutexKV.Unlock(functionName)
   261  
   262  	input := lambda.RemovePermissionInput{
   263  		FunctionName: aws.String(functionName),
   264  		StatementId:  aws.String(d.Id()),
   265  	}
   266  
   267  	if v, ok := d.GetOk("qualifier"); ok {
   268  		input.Qualifier = aws.String(v.(string))
   269  	}
   270  
   271  	log.Printf("[DEBUG] Removing Lambda permission: %s", input)
   272  	_, err := conn.RemovePermission(&input)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
   278  		log.Printf("[DEBUG] Checking if Lambda permission %q is deleted", d.Id())
   279  
   280  		params := &lambda.GetPolicyInput{
   281  			FunctionName: aws.String(d.Get("function_name").(string)),
   282  		}
   283  		if v, ok := d.GetOk("qualifier"); ok {
   284  			params.Qualifier = aws.String(v.(string))
   285  		}
   286  
   287  		log.Printf("[DEBUG] Looking for Lambda permission: %s", *params)
   288  		resp, err := conn.GetPolicy(params)
   289  		if err != nil {
   290  			if awsErr, ok := err.(awserr.Error); ok {
   291  				if awsErr.Code() == "ResourceNotFoundException" {
   292  					return nil
   293  				}
   294  			}
   295  			return resource.NonRetryableError(err)
   296  		}
   297  
   298  		if resp.Policy == nil {
   299  			return nil
   300  		}
   301  
   302  		policyInBytes := []byte(*resp.Policy)
   303  		policy := LambdaPolicy{}
   304  		err = json.Unmarshal(policyInBytes, &policy)
   305  		if err != nil {
   306  			return resource.RetryableError(
   307  				fmt.Errorf("Error unmarshalling Lambda policy: %s", err))
   308  		}
   309  
   310  		_, err = findLambdaPolicyStatementById(&policy, d.Id())
   311  		if err != nil {
   312  			return nil
   313  		}
   314  
   315  		log.Printf("[DEBUG] No error when checking if Lambda permission %s is deleted", d.Id())
   316  		return nil
   317  	})
   318  
   319  	if err != nil {
   320  		return fmt.Errorf("Failed removing Lambda permission: %s", err)
   321  	}
   322  
   323  	log.Printf("[DEBUG] Lambda permission with ID %q removed", d.Id())
   324  	d.SetId("")
   325  
   326  	return nil
   327  }
   328  
   329  func findLambdaPolicyStatementById(policy *LambdaPolicy, id string) (
   330  	*LambdaPolicyStatement, error) {
   331  
   332  	log.Printf("[DEBUG] Received %d statements in Lambda policy: %s", len(policy.Statement), policy.Statement)
   333  	for _, statement := range policy.Statement {
   334  		if statement.Sid == id {
   335  			return &statement, nil
   336  		}
   337  	}
   338  
   339  	return nil, &resource.NotFoundError{
   340  		LastRequest:  id,
   341  		LastResponse: policy,
   342  		Message:      fmt.Sprintf("Failed to find statement %q in Lambda policy:\n%s", id, policy.Statement),
   343  	}
   344  }
   345  
   346  func getQualifierFromLambdaAliasOrVersionArn(arn string) (string, error) {
   347  	matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn)
   348  	if len(matches) < 8 || matches[7] == "" {
   349  		return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)",
   350  			arn)
   351  	}
   352  
   353  	return matches[7], nil
   354  }
   355  
   356  func getFunctionNameFromLambdaArn(arn string) (string, error) {
   357  	matches := regexp.MustCompile(LambdaFunctionRegexp).FindStringSubmatch(arn)
   358  	if len(matches) < 6 || matches[5] == "" {
   359  		return "", fmt.Errorf("Invalid ARN or otherwise unable to get qualifier from ARN (%q)",
   360  			arn)
   361  	}
   362  	return matches[5], nil
   363  }
   364  
   365  type LambdaPolicy struct {
   366  	Version   string
   367  	Statement []LambdaPolicyStatement
   368  	Id        string
   369  }
   370  
   371  type LambdaPolicyStatement struct {
   372  	Condition map[string]map[string]string
   373  	Action    string
   374  	Resource  string
   375  	Effect    string
   376  	Principal map[string]string
   377  	Sid       string
   378  }