google-site-verification: google61fe8ba583a51912.html
AWS SAM Security in Java: Complete Secure Serverless Application Implementation

AWS SAM (Serverless Application Model) security involves securing serverless applications through IAM roles, resource policies, environment variables, and application-level security measures. This guide provides a comprehensive Java implementation for building secure SAM applications.


Overview and Security Concepts

AWS SAM Security Components:

  • IAM Roles & Policies: Least privilege access control
  • Resource Policies: API Gateway, S3, DynamoDB policies
  • Environment Security: Secure environment variables
  • Network Security: VPC, security groups, subnets
  • Application Security: Input validation, encryption, logging

Key Security Areas:

  • Authentication & Authorization: Cognito, API Keys, IAM Auth
  • Data Protection: Encryption at rest and in transit
  • Auditing & Monitoring: CloudTrail, CloudWatch, X-Ray
  • Compliance: GDPR, HIPAA, SOC 2 controls

Dependencies and Setup

Maven Dependencies
<properties> <aws.java.sdk.version>2.20.0</aws.java.sdk.version> <spring.boot.version>3.1.0</spring.boot.version> <aws.lambda.java.version>1.2.2</aws.lambda.java.version> </properties> <dependencies> <!-- AWS SDK v2 --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-core</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>lambda</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>secretsmanager</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>kms</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <!-- AWS Lambda Java Core --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>${aws.lambda.java.version}</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>3.11.0</version> </dependency> <!-- Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> <version>6.1.0</version> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.31</version> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring.boot.version}</version> </dependency> <!-- Logging --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>
SAM Template (template.yml)
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 30 MemorySize: 512 Runtime: java17 Environment: Variables: LOG_LEVEL: INFO POWERTOOLS_SERVICE_NAME: secure-sam-app PARAMETER_STORE_PREFIX: /app/secure-sam Parameters: Environment: Type: String Default: dev AllowedValues: - dev - staging - prod Resources: # Security Roles LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: SecureAppPermissions PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:Query - dynamodb:Scan Resource: !GetRef UsersTable - Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:DeleteObject Resource: !Sub ${SecureBucket.Arn}/* - Effect: Allow Action: - secretsmanager:GetSecretValue - kms:Decrypt Resource: "*" - Effect: Allow Action: - ssm:GetParameter - ssm:GetParameters Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${PARAMETER_STORE_PREFIX}/* # API Gateway with Security SecureApi: Type: AWS::Serverless::Api Properties: StageName: !Ref Environment Auth: DefaultAuthorizer: AWS_IAM AddDefaultAuthorizerToCorsPreflight: false UsagePlan: CreateUsagePlan: SHARED Throttle: BurstLimit: 100 RateLimit: 200 # Lambda Functions UserManagementFunction: Type: AWS::Serverless::Function Properties: CodeUri: target/secure-sam-app-1.0.0.jar Handler: com.example.secureapp.UserManagementHandler::handleRequest Role: !GetAtt LambdaExecutionRole.Arn Events: CreateUser: Type: Api Properties: Path: /users Method: post RestApiId: !Ref SecureApi Auth: Authorizer: AWS_IAM GetUser: Type: Api Properties: Path: /users/{userId} Method: get RestApiId: !Ref SecureApi FileProcessingFunction: Type: AWS::Serverless::Function Properties: CodeUri: target/secure-sam-app-1.0.0.jar Handler: com.example.secureapp.FileProcessingHandler::handleRequest Role: !GetAtt LambdaExecutionRole.Arn VpcConfig: SecurityGroupIds: - !Ref LambdaSecurityGroup SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 Events: ProcessFile: Type: Api Properties: Path: /files Method: post RestApiId: !Ref SecureApi Auth: Authorizer: AWS_IAM # DynamoDB Tables with Encryption UsersTable: Type: AWS::DynamoDB::Table Properties: BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: userId AttributeType: S - AttributeName: email AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH GlobalSecondaryIndexes: - IndexName: EmailIndex KeySchema: - AttributeName: email KeyType: HASH Projection: ProjectionType: ALL SSESpecification: SSEEnabled: true # S3 Bucket with Encryption SecureBucket: Type: AWS::S3::Bucket Properties: BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled # Security Group for Lambda in VPC LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Lambda functions SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 SecurityGroupEgress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 Outputs: ApiUrl: Description: "Secure API Gateway URL" Value: !Sub "https://${SecureApi}.execute-api.${AWS::Region}.amazonaws.com/${Environment}" UserManagementFunction: Description: "User Management Lambda Function ARN" Value: !GetAtt UserManagementFunction.Arn SecureBucketName: Description: "Secure S3 Bucket Name" Value: !Ref SecureBucket

Core Security Components

1. Security Configuration
@Configuration @EnableConfigurationProperties(SamSecurityProperties.class) public class SecurityConfig { @Bean public AwsCredentialsProvider awsCredentialsProvider() { return DefaultCredentialsProvider.create(); } @Bean public DynamoDbClient dynamoDbClient(AwsCredentialsProvider credentialsProvider) { return DynamoDbClient.builder() .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1) .build(); } @Bean public S3Client s3Client(AwsCredentialsProvider credentialsProvider) { return S3Client.builder() .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1) .build(); } @Bean public SecretsManagerClient secretsManagerClient(AwsCredentialsProvider credentialsProvider) { return SecretsManagerClient.builder() .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1) .build(); } @Bean public KmsClient kmsClient(AwsCredentialsProvider credentialsProvider) { return KmsClient.builder() .credentialsProvider(credentialsProvider) .region(Region.US_EAST_1) .build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.registerModule(new JavaTimeModule()); return mapper; } } @ConfigurationProperties(prefix = "app.security") @Data public class SamSecurityProperties { private String kmsKeyId; private String secretsPrefix = "/app/secure-sam"; private int bcryptStrength = 12; private int jwtExpirationMs = 86400000; // 24 hours private String corsAllowedOrigins = "*"; private EncryptionConfig encryption = new EncryptionConfig(); @Data public static class EncryptionConfig { private String algorithm = "AES/GCM/NoPadding"; private int keySize = 256; } }
2. IAM Policy Manager
@Service @Slf4j public class IamPolicyManager { private final ObjectMapper objectMapper; public IamPolicyManager(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public String generateLambdaExecutionPolicy(String tableArn, String bucketArn, String kmsKeyArn, String secretsPrefix) { try { Map<String, Object> policy = new HashMap<>(); policy.put("Version", "2012-10-17"); List<Map<String, Object>> statements = new ArrayList<>(); // DynamoDB permissions statements.add(createStatement( "Allow", List.of("dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan"), List.of(tableArn, tableArn + "/index/*") )); // S3 permissions statements.add(createStatement( "Allow", List.of("s3:GetObject", "s3:PutObject", "s3:DeleteObject"), List.of(bucketArn + "/*") )); // KMS permissions statements.add(createStatement( "Allow", List.of("kms:Decrypt", "kms:GenerateDataKey"), List.of(kmsKeyArn) )); // Secrets Manager permissions statements.add(createStatement( "Allow", List.of("secretsmanager:GetSecretValue"), List.of("arn:aws:secretsmanager:*:*:secret:" + secretsPrefix + "*") )); policy.put("Statement", statements); return objectMapper.writeValueAsString(policy); } catch (Exception e) { log.error("Failed to generate IAM policy", e); throw new RuntimeException("IAM policy generation failed", e); } } public boolean validatePolicy(String policyJson) { try { Map<String, Object> policy = objectMapper.readValue(policyJson, Map.class); // Validate policy structure if (!policy.containsKey("Version") || !policy.containsKey("Statement")) { return false; } List<Map<String, Object>> statements = (List<Map<String, Object>>) policy.get("Statement"); for (Map<String, Object> statement : statements) { if (!statement.containsKey("Effect") || !statement.containsKey("Action") || !statement.containsKey("Resource")) { return false; } } return true; } catch (Exception e) { log.error("Policy validation failed", e); return false; } } private Map<String, Object> createStatement(String effect, List<String> actions, List<String> resources) { Map<String, Object> statement = new HashMap<>(); statement.put("Effect", effect); statement.put("Action", actions); statement.put("Resource", resources); return statement; } public String createResourcePolicy(String sourceArn, String awsAccountId, String region) { try { Map<String, Object> policy = new HashMap<>(); policy.put("Version", "2012-10-17"); List<Map<String, Object>> statements = new ArrayList<>(); Map<String, Object> statement = new HashMap<>(); statement.put("Effect", "Allow"); statement.put("Principal", Map.of("AWS", "*")); statement.put("Action", "execute-api:Invoke"); statement.put("Resource", "execute-api:/*"); // Add condition for source ARN Map<String, Object> condition = new HashMap<>(); condition.put("ArnLike", Map.of("AWS:SourceArn", sourceArn)); statement.put("Condition", condition); statements.add(statement); policy.put("Statement", statements); return objectMapper.writeValueAsString(policy); } catch (Exception e) { log.error("Failed to create resource policy", e); throw new RuntimeException("Resource policy creation failed", e); } } }
3. Secrets Management Service
@Service @Slf4j public class SecretsManagementService { private final SecretsManagerClient secretsManager; private final KmsClient kmsClient; private final SamSecurityProperties securityProperties; public SecretsManagementService(SecretsManagerClient secretsManager, KmsClient kmsClient, SamSecurityProperties securityProperties) { this.secretsManager = secretsManager; this.kmsClient = kmsClient; this.securityProperties = securityProperties; } public String getSecret(String secretName) { try { GetSecretValueRequest request = GetSecretValueRequest.builder() .secretId(securityProperties.getSecretsPrefix() + "/" + secretName) .build(); GetSecretValueResponse response = secretsManager.getSecretValue(request); return response.secretString(); } catch (Exception e) { log.error("Failed to retrieve secret: {}", secretName, e); throw new RuntimeException("Secret retrieval failed: " + secretName, e); } } public void storeSecret(String secretName, String secretValue) { try { // Encrypt the secret before storing String encryptedSecret = encryptSecret(secretValue); CreateSecretRequest request = CreateSecretRequest.builder() .name(securityProperties.getSecretsPrefix() + "/" + secretName) .description("Application secret for " + secretName) .secretString(encryptedSecret) .build(); secretsManager.createSecret(request); log.info("Secret stored successfully: {}", secretName); } catch (Exception e) { log.error("Failed to store secret: {}", secretName, e); throw new RuntimeException("Secret storage failed: " + secretName, e); } } public void rotateSecret(String secretName, String newSecretValue) { try { String encryptedSecret = encryptSecret(newSecretValue); UpdateSecretRequest request = UpdateSecretRequest.builder() .secretId(securityProperties.getSecretsPrefix() + "/" + secretName) .secretString(encryptedSecret) .build(); secretsManager.updateSecret(request); log.info("Secret rotated successfully: {}", secretName); } catch (Exception e) { log.error("Failed to rotate secret: {}", secretName, e); throw new RuntimeException("Secret rotation failed: " + secretName, e); } } public void deleteSecret(String secretName) { try { DeleteSecretRequest request = DeleteSecretRequest.builder() .secretId(securityProperties.getSecretsPrefix() + "/" + secretName) .forceDeleteWithoutRecovery(true) .build(); secretsManager.deleteSecret(request); log.info("Secret deleted successfully: {}", secretName); } catch (Exception e) { log.error("Failed to delete secret: {}", secretName, e); throw new RuntimeException("Secret deletion failed: " + secretName, e); } } private String encryptSecret(String secret) { try { EncryptRequest request = EncryptRequest.builder() .keyId(securityProperties.getKmsKeyId()) .plaintext(SdkBytes.fromUtf8String(secret)) .build(); EncryptResponse response = kmsClient.encrypt(request); return Base64.getEncoder().encodeToString(response.ciphertextBlob().asByteArray()); } catch (Exception e) { log.error("Failed to encrypt secret", e); throw new RuntimeException("Secret encryption failed", e); } } private String decryptSecret(String encryptedSecret) { try { byte[] encryptedBytes = Base64.getDecoder().decode(encryptedSecret); DecryptRequest request = DecryptRequest.builder() .keyId(securityProperties.getKmsKeyId()) .ciphertextBlob(SdkBytes.fromByteArray(encryptedBytes)) .build(); DecryptResponse response = kmsClient.decrypt(request); return response.plaintext().asUtf8String(); } catch (Exception e) { log.error("Failed to decrypt secret", e); throw new RuntimeException("Secret decryption failed", e); } } public Map<String, String> getAllSecrets() { try { ListSecretsRequest request = ListSecretsRequest.builder() .filters(Filter.builder() .key("name") .values(securityProperties.getSecretsPrefix()) .build()) .build(); ListSecretsResponse response = secretsManager.listSecrets(request); return response.secretList().stream() .collect(Collectors.toMap( secret -> secret.name().replace(securityProperties.getSecretsPrefix() + "/", ""), secret -> getSecret(secret.name().replace(securityProperties.getSecretsPrefix() + "/", "")) )); } catch (Exception e) { log.error("Failed to list secrets", e); return Collections.emptyMap(); } } }
4. Encryption Service
@Service @Slf4j public class EncryptionService { private final KmsClient kmsClient; private final SamSecurityProperties securityProperties; public EncryptionService(KmsClient kmsClient, SamSecurityProperties securityProperties) { this.kmsClient = kmsClient; this.securityProperties = securityProperties; } public String encryptData(String plaintext) { try { EncryptRequest request = EncryptRequest.builder() .keyId(securityProperties.getKmsKeyId()) .plaintext(SdkBytes.fromUtf8String(plaintext)) .build(); EncryptResponse response = kmsClient.encrypt(request); return Base64.getEncoder().encodeToString(response.ciphertextBlob().asByteArray()); } catch (Exception e) { log.error("Failed to encrypt data", e); throw new RuntimeException("Data encryption failed", e); } } public String decryptData(String encryptedData) { try { byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData); DecryptRequest request = DecryptRequest.builder() .keyId(securityProperties.getKmsKeyId()) .ciphertextBlob(SdkBytes.fromByteArray(encryptedBytes)) .build(); DecryptResponse response = kmsClient.decrypt(request); return response.plaintext().asUtf8String(); } catch (Exception e) { log.error("Failed to decrypt data", e); throw new RuntimeException("Data decryption failed", e); } } public String generateDataKey() { try { GenerateDataKeyRequest request = GenerateDataKeyRequest.builder() .keyId(securityProperties.getKmsKeyId()) .keySpec(DataKeySpec.AES_256) .build(); GenerateDataKeyResponse response = kmsClient.generateDataKey(request); Map<String, String> dataKey = new HashMap<>(); dataKey.put("ciphertext", Base64.getEncoder().encodeToString(response.ciphertextBlob().asByteArray())); dataKey.put("plaintext", Base64.getEncoder().encodeToString(response.plaintext().asByteArray())); return new ObjectMapper().writeValueAsString(dataKey); } catch (Exception e) { log.error("Failed to generate data key", e); throw new RuntimeException("Data key generation failed", e); } } public boolean validateEncryption(String encryptedData) { try { // Attempt to decrypt to validate the encryption decryptData(encryptedData); return true; } catch (Exception e) { return false; } } public Map<String, String> getEncryptionContext() { return Map.of( "service", "secure-sam-app", "environment", System.getenv("ENVIRONMENT") != null ? System.getenv("ENVIRONMENT") : "dev", "timestamp", Instant.now().toString() ); } }

Lambda Handlers with Security

1. User Management Handler
@Component @Slf4j public class UserManagementHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { private final UserService userService; private final SecurityValidator securityValidator; private final ObjectMapper objectMapper; public UserManagementHandler(UserService userService, SecurityValidator securityValidator, ObjectMapper objectMapper) { this.userService = userService; this.securityValidator = securityValidator; this.objectMapper = objectMapper; } @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { try { log.info("Processing request: {} {}", input.getHttpMethod(), input.getPath()); // Validate IAM authorization if (!securityValidator.validateIamAuthorization(input)) { return createErrorResponse(403, "Access denied"); } String httpMethod = input.getHttpMethod(); String path = input.getPath(); switch (httpMethod) { case "POST": if ("/users".equals(path)) { return createUser(input); } break; case "GET": if (path.startsWith("/users/")) { return getUser(input); } break; case "PUT": if (path.startsWith("/users/")) { return updateUser(input); } break; case "DELETE": if (path.startsWith("/users/")) { return deleteUser(input); } break; } return createErrorResponse(404, "Endpoint not found"); } catch (Exception e) { log.error("Request processing failed", e); return createErrorResponse(500, "Internal server error"); } } private APIGatewayProxyResponseEvent createUser(APIGatewayProxyRequestEvent input) { try { // Validate input if (input.getBody() == null) { return createErrorResponse(400, "Request body is required"); } CreateUserRequest request = objectMapper.readValue(input.getBody(), CreateUserRequest.class); // Validate request Set<ConstraintViolation<CreateUserRequest>> violations = securityValidator.validateRequest(request); if (!violations.isEmpty()) { return createValidationErrorResponse(violations); } // Create user User user = userService.createUser(request); return createSuccessResponse(201, user); } catch (Exception e) { log.error("User creation failed", e); return createErrorResponse(400, "Invalid request format"); } } private APIGatewayProxyResponseEvent getUser(APIGatewayProxyRequestEvent input) { try { String userId = extractUserId(input.getPath()); if (userId == null) { return createErrorResponse(400, "User ID is required"); } // Authorize access - user can only access their own data unless admin if (!securityValidator.canAccessUser(input, userId)) { return createErrorResponse(403, "Access denied to user data"); } User user = userService.getUser(userId); if (user == null) { return createErrorResponse(404, "User not found"); } return createSuccessResponse(200, user); } catch (Exception e) { log.error("User retrieval failed", e); return createErrorResponse(500, "Internal server error"); } } private APIGatewayProxyResponseEvent updateUser(APIGatewayProxyRequestEvent input) { try { String userId = extractUserId(input.getPath()); if (userId == null || input.getBody() == null) { return createErrorResponse(400, "User ID and request body are required"); } // Authorize access if (!securityValidator.canModifyUser(input, userId)) { return createErrorResponse(403, "Access denied to modify user"); } UpdateUserRequest request = objectMapper.readValue(input.getBody(), UpdateUserRequest.class); Set<ConstraintViolation<UpdateUserRequest>> violations = securityValidator.validateRequest(request); if (!violations.isEmpty()) { return createValidationErrorResponse(violations); } User user = userService.updateUser(userId, request); return createSuccessResponse(200, user); } catch (Exception e) { log.error("User update failed", e); return createErrorResponse(400, "Invalid request format"); } } private APIGatewayProxyResponseEvent deleteUser(APIGatewayProxyRequestEvent input) { try { String userId = extractUserId(input.getPath()); if (userId == null) { return createErrorResponse(400, "User ID is required"); } // Authorize access if (!securityValidator.canModifyUser(input, userId)) { return createErrorResponse(403, "Access denied to delete user"); } userService.deleteUser(userId); return createSuccessResponse(204, null); } catch (Exception e) { log.error("User deletion failed", e); return createErrorResponse(500, "Internal server error"); } } private String extractUserId(String path) { // Extract user ID from path like "/users/123" if (path == null || !path.startsWith("/users/")) { return null; } return path.substring("/users/".length()); } private APIGatewayProxyResponseEvent createSuccessResponse(int statusCode, Object body) { try { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setHeaders(createSecurityHeaders()); if (body != null) { response.setBody(objectMapper.writeValueAsString(body)); } return response; } catch (Exception e) { log.error("Failed to create success response", e); return createErrorResponse(500, "Response creation failed"); } } private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setHeaders(createSecurityHeaders()); Map<String, String> errorBody = new HashMap<>(); errorBody.put("error", message); errorBody.put("timestamp", Instant.now().toString()); try { response.setBody(objectMapper.writeValueAsString(errorBody)); } catch (Exception e) { log.error("Failed to serialize error response", e); } return response; } private APIGatewayProxyResponseEvent createValidationErrorResponse(Set<? extends ConstraintViolation<?>> violations) { List<String> errors = violations.stream() .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) .collect(Collectors.toList()); Map<String, Object> errorBody = new HashMap<>(); errorBody.put("error", "Validation failed"); errorBody.put("details", errors); errorBody.put("timestamp", Instant.now().toString()); APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(400); response.setHeaders(createSecurityHeaders()); try { response.setBody(objectMapper.writeValueAsString(errorBody)); } catch (Exception e) { log.error("Failed to serialize validation error response", e); } return response; } private Map<String, String> createSecurityHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Content-Type-Options", "nosniff"); headers.put("X-Frame-Options", "DENY"); headers.put("X-XSS-Protection", "1; mode=block"); headers.put("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); return headers; } }
2. File Processing Handler
@Component @Slf4j public class FileProcessingHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { private final S3Client s3Client; private final EncryptionService encryptionService; private final SecurityValidator securityValidator; private final ObjectMapper objectMapper; private final String bucketName; public FileProcessingHandler(S3Client s3Client, EncryptionService encryptionService, SecurityValidator securityValidator, ObjectMapper objectMapper) { this.s3Client = s3Client; this.encryptionService = encryptionService; this.securityValidator = securityValidator; this.objectMapper = objectMapper; this.bucketName = System.getenv("SECURE_BUCKET_NAME"); } @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { try { log.info("Processing file request: {} {}", input.getHttpMethod(), input.getPath()); // Validate IAM authorization if (!securityValidator.validateIamAuthorization(input)) { return createErrorResponse(403, "Access denied"); } String httpMethod = input.getHttpMethod(); switch (httpMethod) { case "POST": return uploadFile(input); case "GET": return downloadFile(input); case "DELETE": return deleteFile(input); default: return createErrorResponse(405, "Method not allowed"); } } catch (Exception e) { log.error("File processing failed", e); return createErrorResponse(500, "Internal server error"); } } private APIGatewayProxyResponseEvent uploadFile(APIGatewayProxyRequestEvent input) { try { if (input.getBody() == null) { return createErrorResponse(400, "File content is required"); } String fileName = input.getQueryStringParameters() != null ? input.getQueryStringParameters().get("fileName") : null; if (fileName == null || fileName.trim().isEmpty()) { return createErrorResponse(400, "File name is required"); } // Validate file name to prevent path traversal if (!securityValidator.isValidFileName(fileName)) { return createErrorResponse(400, "Invalid file name"); } // Encrypt file content String encryptedContent = encryptionService.encryptData(input.getBody()); // Upload to S3 PutObjectRequest putRequest = PutObjectRequest.builder() .bucket(bucketName) .key("uploads/" + fileName) .sseAlgorithm(ServerSideEncryption.AES256) .metadata(Map.of( "uploaded-by", securityValidator.getCallerIdentity(input), "uploaded-at", Instant.now().toString(), "encrypted", "true" )) .build(); s3Client.putObject(putRequest, RequestBody.fromString(encryptedContent)); Map<String, String> response = new HashMap<>(); response.put("message", "File uploaded successfully"); response.put("fileName", fileName); response.put("uploadedAt", Instant.now().toString()); return createSuccessResponse(201, response); } catch (Exception e) { log.error("File upload failed", e); return createErrorResponse(500, "File upload failed"); } } private APIGatewayProxyResponseEvent downloadFile(APIGatewayProxyRequestEvent input) { try { String fileName = input.getQueryStringParameters() != null ? input.getQueryStringParameters().get("fileName") : null; if (fileName == null || fileName.trim().isEmpty()) { return createErrorResponse(400, "File name is required"); } if (!securityValidator.isValidFileName(fileName)) { return createErrorResponse(400, "Invalid file name"); } // Download from S3 GetObjectRequest getRequest = GetObjectRequest.builder() .bucket(bucketName) .key("uploads/" + fileName) .build(); ResponseBytes<GetObjectResponse> objectBytes = s3Client.getObjectAsBytes(getRequest); String encryptedContent = objectBytes.asUtf8String(); // Decrypt content String decryptedContent = encryptionService.decryptData(encryptedContent); Map<String, String> response = new HashMap<>(); response.put("content", decryptedContent); response.put("fileName", fileName); return createSuccessResponse(200, response); } catch (NoSuchKeyException e) { return createErrorResponse(404, "File not found"); } catch (Exception e) { log.error("File download failed", e); return createErrorResponse(500, "File download failed"); } } private APIGatewayProxyResponseEvent deleteFile(APIGatewayProxyRequestEvent input) { try { String fileName = input.getQueryStringParameters() != null ? input.getQueryStringParameters().get("fileName") : null; if (fileName == null || fileName.trim().isEmpty()) { return createErrorResponse(400, "File name is required"); } if (!securityValidator.isValidFileName(fileName)) { return createErrorResponse(400, "Invalid file name"); } // Check if user has permission to delete this file if (!securityValidator.canModifyFile(input, fileName)) { return createErrorResponse(403, "Access denied to delete file"); } // Delete from S3 DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder() .bucket(bucketName) .key("uploads/" + fileName) .build(); s3Client.deleteObject(deleteRequest); Map<String, String> response = new HashMap<>(); response.put("message", "File deleted successfully"); response.put("fileName", fileName); return createSuccessResponse(200, response); } catch (Exception e) { log.error("File deletion failed", e); return createErrorResponse(500, "File deletion failed"); } } private APIGatewayProxyResponseEvent createSuccessResponse(int statusCode, Object body) { try { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setHeaders(createSecurityHeaders()); if (body != null) { response.setBody(objectMapper.writeValueAsString(body)); } return response; } catch (Exception e) { log.error("Failed to create success response", e); return createErrorResponse(500, "Response creation failed"); } } private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) { APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent(); response.setStatusCode(statusCode); response.setHeaders(createSecurityHeaders()); Map<String, String> errorBody = new HashMap<>(); errorBody.put("error", message); errorBody.put("timestamp", Instant.now().toString()); try { response.setBody(objectMapper.writeValueAsString(errorBody)); } catch (Exception e) { log.error("Failed to serialize error response", e); } return response; } private Map<String, String> createSecurityHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); headers.put("X-Content-Type-Options", "nosniff"); headers.put("X-Frame-Options", "DENY"); headers.put("X-XSS-Protection", "1; mode=block"); return headers; } }
3. Security Validator
@Service @Slf4j public class SecurityValidator { private final ObjectMapper objectMapper; private final Validator validator; public SecurityValidator(ObjectMapper objectMapper) { this.objectMapper = objectMapper; this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } public boolean validateIamAuthorization(APIGatewayProxyRequestEvent request) { try { Map<String, String> identity = request.getRequestContext().getIdentity(); // Check if request is from API Gateway if (identity == null || identity.get("userArn") == null) { log.warn("Missing identity information in request"); return false; } String userArn = identity.get("userArn"); String accessKey = identity.get("accessKey"); // Validate ARN format if (!isValidArn(userArn)) { log.warn("Invalid ARN format: {}", userArn); return false; } // Check if request is from a valid source if (!isValidSource(identity)) { log.warn("Invalid request source: {}", userArn); return false; } log.info("IAM authorization validated for: {}", userArn); return true; } catch (Exception e) { log.error("IAM authorization validation failed", e); return false; } } public boolean canAccessUser(APIGatewayProxyRequestEvent request, String targetUserId) { try { Map<String, String> identity = request.getRequestContext().getIdentity(); String callerArn = identity.get("userArn"); // Extract user ID from ARN (assuming IAM user name is the user ID) String callerUserId = extractUserIdFromArn(callerArn); // User can access their own data if (callerUserId != null && callerUserId.equals(targetUserId)) { return true; } // Check if caller has admin privileges if (hasAdminPrivileges(identity)) { return true; } log.warn("Access denied: {} cannot access user {}", callerArn, targetUserId); return false; } catch (Exception e) { log.error("User access validation failed", e); return false; } } public boolean canModifyUser(APIGatewayProxyRequestEvent request, String targetUserId) { try { Map<String, String> identity = request.getRequestContext().getIdentity(); String callerArn = identity.get("userArn"); // Only allow users to modify their own data or admins String callerUserId = extractUserIdFromArn(callerArn); if (callerUserId != null && callerUserId.equals(targetUserId)) { return true; } if (hasAdminPrivileges(identity)) { return true; } log.warn("Modification denied: {} cannot modify user {}", callerArn, targetUserId); return false; } catch (Exception e) { log.error("User modification validation failed", e); return false; } } public boolean canModifyFile(APIGatewayProxyRequestEvent request, String fileName) { try { // In a real implementation, you would check file ownership/metadata // For now, allow users to modify files they uploaded Map<String, String> identity = request.getRequestContext().getIdentity(); return hasAdminPrivileges(identity); // Only admins can delete files for now } catch (Exception e) { log.error("File modification validation failed", e); return false; } } public boolean isValidFileName(String fileName) { // Prevent path traversal attacks if (fileName == null || fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) { return false; } // Validate file name length and characters if (fileName.length() > 255 || !fileName.matches("^[a-zA-Z0-9._-]+$")) { return false; } return true; } public String getCallerIdentity(APIGatewayProxyRequestEvent request) { Map<String, String> identity = request.getRequestContext().getIdentity(); return identity != null ? identity.get("userArn") : "unknown"; } public <T> Set<ConstraintViolation<T>> validateRequest(T request) { return validator.validate(request); } private boolean isValidArn(String arn) { return arn != null && arn.startsWith("arn:aws:"); } private boolean isValidSource(Map<String, String> identity) { String userAgent = identity.get("userAgent"); String sourceIp = identity.get("sourceIp"); // Validate source IP (example: allow only from specific ranges) if (sourceIp != null && isPrivateIp(sourceIp)) { log.warn("Request from private IP: {}", sourceIp); return false; } // Validate user agent if (userAgent != null && userAgent.contains("MaliciousBot")) { log.warn("Request from malicious user agent: {}", userAgent); return false; } return true; } private boolean isPrivateIp(String ip) { // Check if IP is in private range return ip.startsWith("10.") || ip.startsWith("192.168.") || (ip.startsWith("172.") && isInPrivateRange(ip)); } private boolean isInPrivateRange(String ip) { try { String[] parts = ip.split("\\."); if (parts.length >= 2) { int secondOctet = Integer.parseInt(parts[1]); return secondOctet >= 16 && secondOctet <= 31; } } catch (NumberFormatException e) { // Ignore parsing errors } return false; } private String extractUserIdFromArn(String arn) { if (arn == null) return null; // Extract user name from ARN: arn:aws:iam::123456789012:user/username String[] parts = arn.split("/"); return parts.length > 1 ? parts[1] : null; } private boolean hasAdminPrivileges(Map<String, String> identity) { String userArn = identity.get("userArn"); // Check if user is in admin group (simplified) // In real implementation, you would check IAM policies return userArn != null && userArn.contains("Admin"); } }

Domain Models

1. Request/Response Models
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class CreateUserRequest { @NotBlank(message = "Username is required") @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") @Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "Username can only contain letters, numbers, underscores, and hyphens") private String username; @NotBlank(message = "Email is required") @Email(message = "Email must be valid") private String email; @NotBlank(message = "Password is required") @Size(min = 8, message = "Password must be at least 8 characters long") @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$", message = "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character") private String password; @NotBlank(message = "First name is required") @Size(max = 100, message = "First name cannot exceed 100 characters") private String firstName; @NotBlank(message = "Last name is required") @Size(max = 100, message = "Last name cannot exceed 100 characters") private String lastName; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class UpdateUserRequest { @Email(message = "Email must be valid") private String email; @Size(max = 100, message = "First name cannot exceed 100 characters") private String firstName; @Size(max = 100, message = "Last name cannot exceed 100 characters") private String lastName; } @Data @Builder @NoArgsConstructor @AllArgsConstructor public class User { private String userId; private String username; private String email; private String firstName; private String lastName; private Instant createdAt; private Instant updatedAt; private boolean active; private List<String> roles; }
2. User Service
@Service @Slf4j public class UserService { private final DynamoDbClient dynamoDbClient; private final PasswordEncoder passwordEncoder; private final EncryptionService encryptionService; private final String tableName; public UserService(DynamoDbClient dynamoDbClient, PasswordEncoder passwordEncoder, EncryptionService encryptionService) { this.dynamoDbClient = dynamoDbClient; this.passwordEncoder = passwordEncoder; this.encryptionService = encryptionService; this.tableName = System.getenv("USERS_TABLE_NAME"); } public User createUser(CreateUserRequest request) { try { // Check if user already exists if (userExists(request.getUsername(), request.getEmail())) { throw new RuntimeException("User already exists"); } String userId = UUID.randomUUID().toString(); Instant now = Instant.now(); // Hash password String hashedPassword = passwordEncoder.encode(request.getPassword()); // Encrypt sensitive data String encryptedEmail = encryptionService.encryptData(request.getEmail()); // Create user item Map<String, AttributeValue> item = new HashMap<>(); item.put("userId", AttributeValue.builder().s(userId).build()); item.put("username", AttributeValue.builder().s(request.getUsername()).build()); item.put("email", AttributeValue.builder().s(encryptedEmail).build()); item.put("firstName", AttributeValue.builder().s(request.getFirstName()).build()); item.put("lastName", AttributeValue.builder().s(request.getLastName()).build()); item.put("passwordHash", AttributeValue.builder().s(hashedPassword).build()); item.put("createdAt", AttributeValue.builder().s(now.toString()).build()); item.put("updatedAt", AttributeValue.builder().s(now.toString()).build()); item.put("active", AttributeValue.builder().bool(true).build()); item.put("roles", AttributeValue.builder().ss("USER").build()); PutItemRequest putRequest = PutItemRequest.builder() .tableName(tableName) .item(item) .build(); dynamoDbClient.putItem(putRequest); log.info("User created successfully: {}", userId); return User.builder() .userId(userId) .username(request.getUsername()) .email(request.getEmail()) // Return decrypted email .firstName(request.getFirstName()) .lastName(request.getLastName()) .createdAt(now) .updatedAt(now) .active(true) .roles(List.of("USER")) .build(); } catch (Exception e) { log.error("Failed to create user", e); throw new RuntimeException("User creation failed", e); } } public User getUser(String userId) { try { GetItemRequest getRequest = GetItemRequest.builder() .tableName(tableName) .key(Map.of("userId", AttributeValue.builder().s(userId).build())) .build(); GetItemResponse response = dynamoDbClient.getItem(getRequest); if (response.item() == null || response.item().isEmpty()) { return null; } return mapToUser(response.item()); } catch (Exception e) { log.error("Failed to get user: {}", userId, e); throw new RuntimeException("User retrieval failed", e); } } public User updateUser(String userId, UpdateUserRequest request) { try { // Build update expression StringBuilder updateExpression = new StringBuilder("SET updatedAt = :updatedAt"); Map<String, String> expressionAttributeNames = new HashMap<>(); Map<String, AttributeValue> expressionAttributeValues = new HashMap<>(); expressionAttributeValues.put(":updatedAt", AttributeValue.builder().s(Instant.now().toString()).build()); if (request.getEmail() != null) { updateExpression.append(", email = :email"); expressionAttributeValues.put(":email", AttributeValue.builder().s(encryptionService.encryptData(request.getEmail())).build()); } if (request.getFirstName() != null) { updateExpression.append(", firstName = :firstName"); expressionAttributeValues.put(":firstName", AttributeValue.builder().s(request.getFirstName()).build()); } if (request.getLastName() != null) { updateExpression.append(", lastName = :lastName"); expressionAttributeValues.put(":lastName", AttributeValue.builder().s(request.getLastName()).build()); } UpdateItemRequest updateRequest = UpdateItemRequest.builder() .tableName(tableName) .key(Map.of("userId", AttributeValue.builder().s(userId).build())) .updateExpression(updateExpression.toString()) .expressionAttributeValues(expressionAttributeValues) .returnValues(ReturnValue.ALL_NEW) .build(); UpdateItemResponse response = dynamoDbClient.updateItem(updateRequest); return mapToUser(response.attributes()); } catch (Exception e) { log.error("Failed to update user: {}", userId, e); throw new RuntimeException("User update failed", e); } } public void deleteUser(String userId) { try { DeleteItemRequest deleteRequest = DeleteItemRequest.builder() .tableName(tableName) .key(Map.of("userId", AttributeValue.builder().s(userId).build())) .build(); dynamoDbClient.deleteItem(deleteRequest); log.info("User deleted successfully: {}", userId); } catch (Exception e) { log.error("Failed to delete user: {}", userId, e); throw new RuntimeException("User deletion failed", e); } } private boolean userExists(String username, String email) { try { // Check by username QueryRequest queryRequest = QueryRequest.builder() .tableName(tableName) .indexName("UsernameIndex") .keyConditionExpression("username = :username") .expressionAttributeValues(Map.of( ":username", AttributeValue.builder().s(username).build() )) .build(); QueryResponse response = dynamoDbClient.query(queryRequest); if (response.count() > 0) { return true; } // Check by email (encrypted) String encryptedEmail = encryptionService.encryptData(email); QueryRequest emailQueryRequest = QueryRequest.builder() .tableName(tableName) .indexName("EmailIndex") .keyConditionExpression("email = :email") .expressionAttributeValues(Map.of( ":email", AttributeValue.builder().s(encryptedEmail).build() )) .build(); QueryResponse emailResponse = dynamoDbClient.query(emailQueryRequest); return emailResponse.count() > 0; } catch (Exception e) { log.error("Failed to check user existence", e); throw new RuntimeException("User existence check failed", e); } } private User mapToUser(Map<String, AttributeValue> item) { try { String encryptedEmail = item.get("email").s(); String decryptedEmail = encryptionService.decryptData(encryptedEmail); return User.builder() .userId(item.get("userId").s()) .username(item.get("username").s()) .email(decryptedEmail) .firstName(item.get("firstName").s()) .lastName(item.get("lastName").s()) .createdAt(Instant.parse(item.get("createdAt").s())) .updatedAt(Instant.parse(item.get("updatedAt").s())) .active(item.get("active").bool()) .roles(item.get("roles").ss()) .build(); } catch (Exception e) { log.error("Failed to map DynamoDB item to User", e); throw new RuntimeException("User mapping failed", e); } } }

Security Monitoring and Auditing

1. Security Audit Service
@Service @Slf4j public class SecurityAuditService { private final CloudWatchLogsClient cloudWatchLogsClient; private final String logGroupName; public SecurityAuditService(CloudWatchLogsClient cloudWatchLogsClient) { this.cloudWatchLogsClient = cloudWatchLogsClient; this.logGroupName = "/aws/lambda/secure-sam-app"; } public void logSecurityEvent(SecurityEvent event) { try { PutLogEventsRequest request = PutLogEventsRequest.builder() .logGroupName(logGroupName) .logStreamName("security-events") .logEvents(InputLogEvent.builder() .message(event.toJson()) .timestamp(System.currentTimeMillis()) .build()) .build(); cloudWatchLogsClient.putLogEvents(request); } catch (Exception e) { log.error("Failed to log security event", e); // Fallback to standard logging log.warn("Security Event: {}", event.toJson()); } } public void logApiAccess(APIGatewayProxyRequestEvent request, int statusCode) { SecurityEvent event = SecurityEvent.builder() .eventType("API_ACCESS") .timestamp(Instant.now()) .principal(getCallerIdentity(request)) .resource(request.getPath()) .action(request.getHttpMethod()) .status(statusCode >= 200 && statusCode < 300 ? "SUCCESS" : "FAILED") .details(Map.of( "userAgent", request.getRequestContext().getIdentity().get("userAgent"), "sourceIp", request.getRequestContext().getIdentity().get("sourceIp"), "statusCode", String.valueOf(statusCode) )) .build(); logSecurityEvent(event); } public void logSecurityViolation(APIGatewayProxyRequestEvent request, String violationType) { SecurityEvent event = SecurityEvent.builder() .eventType("SECURITY_VIOLATION") .timestamp(Instant.now()) .principal(getCallerIdentity(request)) .resource(request.getPath()) .action(request.getHttpMethod()) .status("BLOCKED") .details(Map.of( "violationType", violationType, "userAgent", request.getRequestContext().getIdentity().get("userAgent"), "sourceIp", request.getRequestContext().getIdentity().get("sourceIp") )) .build(); logSecurityEvent(event); } private String getCallerIdentity(APIGatewayProxyRequestEvent request) { Map<String, String> identity = request.getRequestContext().getIdentity(); return identity != null ? identity.get("userArn") : "unknown"; } } @Data @Builder @NoArgsConstructor @AllArgsConstructor class SecurityEvent { private String eventType; private Instant timestamp; private String principal; private String resource; private String action; private String status; private Map<String, String> details; public String toJson() { try { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); return mapper.writeValueAsString(this); } catch (Exception e) { return "{\"error\": \"Failed to serialize security event\"}"; } } }

Testing

1. Unit Tests
@ExtendWith(MockitoExtension.class) class UserManagementHandlerTest { @Mock private UserService userService; @Mock private SecurityValidator securityValidator; @InjectMocks private UserManagementHandler handler; private ObjectMapper objectMapper = new ObjectMapper(); @Test void testCreateUser_Success() throws Exception { // Setup CreateUserRequest request = CreateUserRequest.builder() .username("testuser") .email("[email protected]") .password("SecurePass123!") .firstName("Test") .lastName("User") .build(); User user = User.builder() .userId("123") .username("testuser") .email("[email protected]") .build(); APIGatewayProxyRequestEvent apiRequest = new APIGatewayProxyRequestEvent(); apiRequest.setHttpMethod("POST"); apiRequest.setPath("/users"); apiRequest.setBody(objectMapper.writeValueAsString(request)); when(securityValidator.validateIamAuthorization(any())).thenReturn(true); when(securityValidator.validateRequest(any())).thenReturn(Collections.emptySet()); when(userService.createUser(any())).thenReturn(user); // Execute APIGatewayProxyResponseEvent response = handler.handleRequest(apiRequest, null); // Verify assertEquals(201, response.getStatusCode()); verify(userService).createUser(any()); } @Test void testCreateUser_Unauthorized() { // Setup APIGatewayProxyRequestEvent apiRequest = new APIGatewayProxyRequestEvent(); apiRequest.setHttpMethod("POST"); apiRequest.setPath("/users"); when(securityValidator.validateIamAuthorization(any())).thenReturn(false); // Execute APIGatewayProxyResponseEvent response = handler.handleRequest(apiRequest, null); // Verify assertEquals(403, response.getStatusCode()); verify(userService, never()).createUser(any()); } } @SpringBootTest class SecurityValidatorTest { @Autowired private SecurityValidator securityValidator; @Test void testValidFileName() { assertTrue(securityValidator.isValidFileName("document.pdf")); assertTrue(securityValidator.isValidFileName("image_123.jpg")); } @Test void testInvalidFileName() { assertFalse(securityValidator.isValidFileName("../etc/passwd")); assertFalse(securityValidator.isValidFileName("file/with/slashes.txt")); assertFalse(securityValidator.isValidFileName("file\\with\\backslashes.txt")); } }
2. Integration Tests
@SpringBootTest @TestPropertySource(properties = { "app.security.kms-key-id=test-key", "app.security.secrets-prefix=/test/app" }) class SecureSamApplicationTest { @Autowired private UserManagementHandler userManagementHandler; @Test void testApplicationContextLoads() { assertNotNull(userManagementHandler); } }

Best Practices

  1. IAM Security:
  • Principle of least privilege
  • Use resource-based policies where possible
  • Regular permission reviews
  1. Data Protection:
  • Encrypt sensitive data at rest and in transit
  • Use AWS KMS for key management
  • Implement proper key rotation
  1. Network Security:
  • Use VPC for Lambda functions when needed
  • Implement security groups and NACLs
  • Use private subnets for databases
  1. Monitoring & Auditing:
  • Enable CloudTrail for API logging
  • Use CloudWatch for monitoring
  • Implement custom security events
  1. Application Security:
  • Input validation and sanitization
  • Secure headers implementation
  • Proper error handling without information leakage
// Example of secure environment configuration @Component public class SecureEnvironmentConfig { public void validateEnvironment() { validateRequiredVars("USERS_TABLE_NAME", "SECURE_BUCKET_NAME"); validateKmsKey(); validateSecretsManager(); } private void validateRequiredVars(String... vars) { for (String var : vars) { if (System.getenv(var) == null) { throw new IllegalStateException("Required environment variable missing: " + var); } } } }

Conclusion

This AWS SAM security implementation provides:

  • Comprehensive IAM security with least privilege principles
  • End-to-end encryption for data protection
  • Secure API Gateway configuration with IAM authorization
  • Proper secret management using AWS Secrets Manager
  • Security monitoring and auditing capabilities
  • Input validation and sanitization at all layers

The solution follows AWS security best practices and can be extended with additional security measures like WAF, Shield, and GuardDuty for enterprise-grade security in serverless applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper