Skip to content

Commit

Permalink
Merge pull request #84 from AlibabaCloudLandingZone/solution-access-a…
Browse files Browse the repository at this point in the history
…nalyzer-external-access/0.0.1

solution-access-analyzer-external-access/0.0.1
  • Loading branch information
wibud authored Jan 2, 2025
2 parents a4a64bb + eec0f65 commit ef81d0c
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 0 deletions.
23 changes: 23 additions & 0 deletions solution/solution-access-analyzer-external-access/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
dependency-reduced-pom.xml

### IntelliJ IDEA ###
.idea/

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store
25 changes: 25 additions & 0 deletions solution/solution-access-analyzer-external-access/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 通过访问分析识别和收敛外部访问

企业在日常用云的过程中,为保证业务的灵活性,通常会出现外部访问授权的情形,如账号A会将部分云资源授权给账号B乃至多个账号使用,运维团队对此难以及时跟踪,存在安全风险。为了既不影响业务协同的灵活性,又保障企业账号的安全,本方案介绍如何通过Access Analyzer访问分析服务来帮助企业及时发现存在跨账号访问权限的资源,从而判断该访问的合理性并收敛非必要的外部访问权限。

访问分析通过和事件总线集成,支持您使用事件总线接收访问分析的分析结果事件,并执行您自己的自动化程序,实现访问分析结果的自动修复和处理。这里针对最常见的OSS Bucket非预期公开访问的场景,通过访问分析、事件总线配合函数计算,实现自动化阻止OSS Bucket公开访问,避免非预期的公开访问,导致企业数据泄露,及时保证数据安全。

本方案提供基于Terraform的自动化模板,为资源目录下的目标账号批量创建RAM角色并提供函数计算示例代码。

## 使用步骤

### 自动化部署代码

本方案提供了基于ROS的批量跨账号创建角色的Terraform自动化代码,模版代码所在目录为`ros/create-role-cross-account`,其模版入参输入:

| **参数名称** | **参数值示例** | **描述** |
| --- | --- | --- |
| role_name | CentralizedOperationRole | 批量创建的角色的名称 |
| policy_name | CentralizedOperationRolePolicy | 绑定到该角色的权限策略的名称 |
| policy_document | {"Version":"1","Statement":[{"Action":"ecs:*","Resource":"*","Effect":"Allow"}]} | 绑定到该角色的权限策略内容。既跨账号资源操作的所有权限。|
| assume_role_principal_account | 1254004******** | 角色的可信账号,既允许扮演到该新建角色的账号,不填默认为当前账号|
| assume_role_principal_role | AssumeRoleName | 允许可信账号下扮演到该角色的角色名称。请确保该角色在可信账号下已经存在,否则会创建失败。 |

### 函数计算代码示例

本方案提供了基于函数计算自动阻止OSS Bucket公共访问的示例代码,代码所在目录为`fc/java`。您可以使用命令`mvn -U clean package`将代码构建为JAR包,然后上传至阿里云函数计算。
93 changes: 93 additions & 0 deletions solution/solution-access-analyzer-external-access/fc/java/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>fc-java</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
<exec.cleanupDaemonThreads>false</exec.cleanupDaemonThreads>
</properties>

<dependencies>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-sts</artifactId>
<version>3.1.2</version>
</dependency>

<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>[4.0.0,5.0.0)</version>
</dependency>

<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.18.1</version>
</dependency>

<dependency>
<groupId>com.aliyun.fc.runtime</groupId>
<artifactId>fc-java-core</artifactId>
<version>1.4.1</version>
</dependency>

<dependency>
<groupId>com.aliyun.fc.runtime</groupId>
<artifactId>fc-java-event</artifactId>
<version>1.2.0</version>
</dependency>

<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.example;

import com.alibaba.fastjson2.JSON;
import com.aliyun.fc.runtime.Context;
import com.aliyun.fc.runtime.FunctionComputeLogger;
import com.aliyun.fc.runtime.StreamRequestHandler;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutBucketPublicAccessBlockRequest;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
import lombok.Data;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class Main implements StreamRequestHandler {

@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
FunctionComputeLogger logger = context.getLogger();
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int length; (length = inputStream.read(buffer)) != -1; ) {
result.write(buffer, 0, length);
}
String event = result.toString(StandardCharsets.UTF_8.name());
logger.error(event);
MessageData messageData = JSON.parseObject(event, MessageData.class);

// get credentials from context
com.aliyun.fc.runtime.Credentials creds = context.getExecutionCredentials();

// cross account
DefaultProfile profile = DefaultProfile.getProfile(System.getenv("REGION"), creds.getAccessKeyId(),
creds.getAccessKeySecret(), creds.getSecurityToken());
IAcsClient client = new DefaultAcsClient(profile);
AssumeRoleRequest request = new AssumeRoleRequest();
request.setRoleArn(String.format("acs:ram::%s:role/%s", messageData.getResourceOwnerAccountId(), System.getenv("ASSUME_ROLE_NAME")));
request.setRoleSessionName("AccessAnalyzer");

try {
AssumeRoleResponse response = client.getAcsResponse(request);

ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
configuration.setSignatureVersion(SignVersion.V4);

String[] resourceArns = messageData.getResourceArn().split(":");
String ossRegion = resourceArns[2];
String ossBucket = resourceArns[4];

OSS ossClient = OSSClientBuilder.create()
.endpoint(String.format("https://%s.aliyuncs.com", ossRegion))
.credentialsProvider(new DefaultCredentialProvider(response.getCredentials().getAccessKeyId(), response.getCredentials().getAccessKeySecret(), response.getCredentials().getSecurityToken()))
.clientConfiguration(configuration)
.region(ossRegion.replace("oss-", ""))
.build();
ossClient.putBucketPublicAccessBlock(new PutBucketPublicAccessBlockRequest(ossBucket, true));
} catch (Exception e) {
logger.error(e.getMessage());
}
}

@Data
class MessageData {

String resourceOwnerAccountId;

String resourceType;

String isPublic;

String status;

Boolean isDeleted;

String resourceArn;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
resource "alicloud_ram_role" "role" {
name = var.role_name
document = <<EOF
{
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"RAM": [
"acs:ram::${var.assume_role_principal_account == "" ? var.ALIYUN__AccountId : var.assume_role_principal_account}:role/${var.assume_role_principal_role}"
]
}
}
],
"Version": "1"
}
EOF
}

resource "alicloud_ram_policy" "policy" {
policy_name = var.policy_name
policy_document = jsonencode(var.policy_document)
}

resource "alicloud_ram_role_policy_attachment" "attach" {
policy_name = alicloud_ram_policy.policy.name
policy_type = alicloud_ram_policy.policy.type
role_name = alicloud_ram_role.role.name

depends_on = [alicloud_ram_policy.policy, alicloud_ram_role.role]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "role_arn" {
value = alicloud_ram_role.role.arn
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
variable "ALIYUN__AccountId" {
type = string
}

variable "role_name" {
type = string
description = <<EOT
{
"ConstraintDescription": {
"zh-cn": "不得超过 64 个字符、英文字母、数字或'-'。",
"en": "No more than 64 characters,English letters, Numbers, or '-' are allowed."
},
"Description": {
"zh-cn": "角色的名称,如果已经存在,请更改名称,<br>由英文字母、数字或'-'组成,不超过64个字符。",
"en": "The name of role, Change the name if it already exists,<br>Consist of english letters, numbers or '-',not more than 64 characters."
},
"MinLength": 1,
"Label": {
"zh-cn": "角色的名称",
"en": "Role Name"
},
"AllowedPattern": "^[a-zA-Z0-9\\-]+$",
"MaxLength": 64,
"Type": "String"
}
EOT
}

variable "policy_name" {
type = string
description = <<EOT
{
"Type": "String",
"Description": {
"zh-cn": "策略名,改变名称如果它已经存在,<br>由英文字母,数字或'-',5-128个字符组成。",
"en": "The policy name, Change the name if it already exists,<br>Consist of english letters, numbers or '-', 5-128 characters."
},
"MinLength": 5,
"Label": {
"zh-cn": "策略名",
"en": "Policy Name"
},
"AllowedPattern": "^[a-zA-Z0-9\\-]+$",
"MaxLength": 128,
"ConstraintDescription": {
"zh-cn": "由英文字母、数字或'-',5-128个字符组成。",
"en": "Consist of english letters, numbers or '-',5-128 characters."
}
}
EOT
}

variable "policy_document" {
type = any
description = <<EOT
{
"Type": "Json",
"Description": {
"zh-cn": "策略内容。其中 Action 和 Resource 必须配置为数组格式。",
"en": "The policy document. Action and Resource must be configured in array format."
},
"Label": {
"zh-cn": "策略内容",
"en": "Policy Document"
}
}
EOT
}

variable "assume_role_principal_account" {
type = string
default = ""
description = <<EOT
{
"Default": "",
"Type": "String",
"Description": {
"zh-cn": "该角色可信的账号。置空,则默认为当前账号。",
"en": "The trusted account for this role. Default is current account while empty."
},
"Label": {
"zh-cn": "角色可信的账号",
"en": "Principal Account"
}
}
EOT
}

variable "assume_role_principal_role" {
type = string
description = <<EOT
{
"Type": "String",
"Description": {
"zh-cn": "允许扮演该角色的可信账号下的角色。请确保该角色在可信账号下已经存在,否则会创建失败。",
"en": "Role of trusted account that are allowed to assume this role."
},
"Label": {
"zh-cn": "可信账号下允许扮演的角色",
"en": "Principal Role"
}
}
EOT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_providers {
alicloud = {
source = "aliyun/alicloud"
version = ">=1.213.1"
}
}
required_version = ">=0.13"
}

0 comments on commit ef81d0c

Please sign in to comment.