Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resource permission to lambda #685

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions src/main/java/gyro/aws/lambda/FunctionPermission.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package gyro.aws.lambda;

import java.util.Iterator;

import com.fasterxml.jackson.databind.JsonNode;
import gyro.aws.Copyable;
import gyro.core.resource.Diffable;
import gyro.core.resource.Updatable;
import gyro.core.validation.Required;
import software.amazon.awssdk.services.lambda.model.AddPermissionRequest;
import software.amazon.awssdk.services.lambda.model.FunctionUrlAuthType;

public class FunctionPermission extends Diffable implements Copyable<AddPermissionRequest> {

private String functionName;
private String statementId;
private String action;
private String principal;
private String sourceArn;
private String sourceAccount;
private String eventSourceToken;
private String qualifier;
private String revisionId;
private String principalOrgID;
private FunctionUrlAuthType functionUrlAuthType;

/**
* The name of the Lambda function.
*/
@Updatable
public String getFunctionName() {
return functionName;
}

public void setFunctionName(String functionName) {
this.functionName = functionName;
}

/**
* A unique statement identifier.
*/
@Required
public String getStatementId() {
return statementId;
}

public void setStatementId(String statementId) {
this.statementId = statementId;
}

/**
* The action that the principal can perform.
*/
@Updatable
public String getAction() {
return action;
}

public void setAction(String action) {
this.action = action;
}

/**
* The principal who is getting this permission.
*/
@Updatable
public String getPrincipal() {
return principal;
}

public void setPrincipal(String principal) {
this.principal = principal;
}

/**
* The ARN of the source that is invoking the function.
*/
@Updatable
public String getSourceArn() {
return sourceArn;
}

public void setSourceArn(String sourceArn) {
this.sourceArn = sourceArn;
}

/**
* The AWS account ID of the source that is invoking the function.
*/
@Updatable
public String getSourceAccount() {
return sourceAccount;
}

public void setSourceAccount(String sourceAccount) {
this.sourceAccount = sourceAccount;
}

/**
* The token that must be present in the request.
*/
@Updatable
public String getEventSourceToken() {
return eventSourceToken;
}

public void setEventSourceToken(String eventSourceToken) {
this.eventSourceToken = eventSourceToken;
}

/**
* The version or alias of the function.
*/
@Updatable
public String getQualifier() {
return qualifier;
}

public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}

/**
* The revision ID of the function.
*/
@Updatable
public String getRevisionId() {
return revisionId;
}

public void setRevisionId(String revisionId) {
this.revisionId = revisionId;
}

/**
* The organization ID of the principal.
*/
@Updatable
public String getPrincipalOrgID() {
return principalOrgID;
}

public void setPrincipalOrgID(String principalOrgID) {
this.principalOrgID = principalOrgID;
}

/**
* The type of authentication to use.
*/
@Updatable
public FunctionUrlAuthType getFunctionUrlAuthType() {
return functionUrlAuthType;
}

public void setFunctionUrlAuthType(FunctionUrlAuthType functionUrlAuthType) {
this.functionUrlAuthType = functionUrlAuthType;
}

@Override
public String primaryKey() {
return getStatementId();
}

@Override
public void copyFrom(AddPermissionRequest model) {
setFunctionName(model.functionName());
setStatementId(model.statementId());
setAction(model.action());
setPrincipal(model.principal());
setSourceArn(model.sourceArn());
setSourceAccount(model.sourceAccount());
setEventSourceToken(model.eventSourceToken());
setQualifier(model.qualifier());
setRevisionId(model.revisionId());
setPrincipalOrgID(model.principalOrgID());
setFunctionUrlAuthType(model.functionUrlAuthType());
}

AddPermissionRequest toAddPermissionRequest() {
return AddPermissionRequest.builder()
.functionName(getFunctionName())
.statementId(getStatementId())
.action(getAction())
.principal(getPrincipal())
.sourceArn(getSourceArn())
.sourceAccount(getSourceAccount())
.eventSourceToken(getEventSourceToken())
.qualifier(getQualifier())
.revisionId(getRevisionId())
.principalOrgID(getPrincipalOrgID())
.functionUrlAuthType(getFunctionUrlAuthType())
.build();
}

protected static AddPermissionRequest getAddPermissionRequest(JsonNode statement) {
AddPermissionRequest.Builder builder = AddPermissionRequest.builder();

builder.statementId(statement.get("Sid").asText())
.action(statement.get("Action").asText())
.principal(statement.get("Principal").get("Service").asText())
.functionName(statement.get("Resource").asText());

if (statement.has("Condition")) {
JsonNode condition = statement.get("Condition");
Iterator<String> fieldNames = condition.fieldNames();

while (fieldNames.hasNext()) {
String conditionKey = fieldNames.next();
JsonNode conditionValue = condition.get(conditionKey);

if (conditionKey.equals("ArnLike") && conditionValue.has("AWS:SourceArn")) {
builder.sourceArn(conditionValue.get("AWS:SourceArn").asText());
} else if (conditionKey.equals("StringEquals") && conditionValue.has("AWS:SourceAccount")) {
builder.sourceAccount(conditionValue.get("AWS:SourceAccount").asText());
}
}
}

if (statement.has("Qualifier")) {
builder.qualifier(statement.get("Qualifier").asText());
}
if (statement.has("FunctionUrlAuthType")) {
builder.functionUrlAuthType(FunctionUrlAuthType.fromValue(statement.get("FunctionUrlAuthType").asText()));
}
if (statement.has("EventSourceToken")) {
builder.eventSourceToken(statement.get("EventSourceToken").asText());
}
if (statement.has("RevisionId")) {
builder.revisionId(statement.get("RevisionId").asText());
}
if (statement.has("PrincipalOrgID")) {
builder.principalOrgID(statement.get("PrincipalOrgID").asText());
}

return builder.build();
}
}
80 changes: 79 additions & 1 deletion src/main/java/gyro/aws/lambda/FunctionResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package gyro.aws.lambda;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.psddev.dari.util.ObjectUtils;
import gyro.aws.AwsResource;
import gyro.aws.Copyable;
Expand All @@ -38,10 +40,12 @@
import org.apache.commons.codec.digest.DigestUtils;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.AddPermissionRequest;
import software.amazon.awssdk.services.lambda.model.CreateFunctionRequest;
import software.amazon.awssdk.services.lambda.model.CreateFunctionResponse;
import software.amazon.awssdk.services.lambda.model.FunctionConfiguration;
import software.amazon.awssdk.services.lambda.model.GetFunctionResponse;
import software.amazon.awssdk.services.lambda.model.GetPolicyResponse;
import software.amazon.awssdk.services.lambda.model.ListTagsResponse;
import software.amazon.awssdk.services.lambda.model.ListVersionsByFunctionResponse;
import software.amazon.awssdk.services.lambda.model.ResourceNotFoundException;
Expand Down Expand Up @@ -107,6 +111,7 @@ public class FunctionResource extends AwsResource implements Copyable<FunctionCo
private Integer reservedConcurrentExecutions;
private Map<String, String> versionMap;
private Boolean publish;
private Set<FunctionPermission> permission;

// -- Readonly

Expand Down Expand Up @@ -438,6 +443,22 @@ public void setPublish(Boolean publish) {
this.publish = publish;
}

/**
* The set of permissions to be associated with the Lambda Function.
*/
@Updatable
public Set<FunctionPermission> getPermission() {
if (permission == null) {
permission = new HashSet<>();
}

return permission;
}

public void setPermission(Set<FunctionPermission> permission) {
this.permission = permission;
}

/**
* The arn for the lambda Lambda Function resource including the version.
*/
Expand Down Expand Up @@ -571,6 +592,17 @@ public void copyFrom(FunctionConfiguration configuration) {
);

setReservedConcurrentExecutions(response.concurrency() != null ? response.concurrency().reservedConcurrentExecutions() : null);

getPermission().clear();
try {
GetPolicyResponse policy = client.getPolicy(r -> r.functionName(getName()));

if (policy.policy() != null) {
setPolicy(policy.policy());
}
} catch (ResourceNotFoundException ex) {
// Ignore
}
}

@Override
Expand Down Expand Up @@ -647,18 +679,29 @@ public void create(GyroUI ui, State state) {
setVersion(response.version());
setCodeHash(response.codeSha256());

setVersions(client);
state.save();

if (getReservedConcurrentExecutions() != null) {
try {
client.putFunctionConcurrency(
r -> r.functionName(getName())
.reservedConcurrentExecutions(getReservedConcurrentExecutions())
);

state.save();
} catch (Exception ex) {
ui.write("\n@|bold,red Error assigning reserved concurrency executions to lambda function %s. Error - %s|@", getArn(), ex.getMessage());
}
}

setVersions(client);
if (!getPermission().isEmpty()) {
for (FunctionPermission permission : getPermission()) {
client.addPermission(permission.toAddPermissionRequest());
}

state.save();
}
}

@Override
Expand All @@ -671,6 +714,23 @@ public void update(GyroUI ui, State state, Resource resource, Set<String> change

Set<String> changeSet = new HashSet<>(changedFieldNames);

if (changeSet.contains("permission")) {
if (!oldResource.getPermission().isEmpty()) {
for (FunctionPermission permission : oldResource.getPermission()) {
client.removePermission(
r -> r.functionName(getName())
.statementId(permission.getStatementId())
);
}
}

for (FunctionPermission permission : getPermission()) {
client.addPermission(permission.toAddPermissionRequest());
}

changeSet.remove("permission");
}

if (changeSet.contains("reserved-concurrent-executions")) {
if (getReservedConcurrentExecutions() != null) {
client.putFunctionConcurrency(
Expand Down Expand Up @@ -820,4 +880,22 @@ private void setVersions(LambdaClient client) {
}
}
}

private void setPolicy(String jsonPolicy) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode policyNode = objectMapper.readTree(jsonPolicy);
JsonNode statements = policyNode.get("Statement");

for (JsonNode statement : statements) {
AddPermissionRequest request = FunctionPermission.getAddPermissionRequest(statement);
FunctionPermission permission = newSubresource(FunctionPermission.class);
permission.copyFrom(request);
getPermission().add(permission);
}

} catch (Exception e) {
throw new GyroException("Error parsing function policy",e);
}
}
}
Loading
Loading