[cloud] Support creation of a censorship bypass role for Alibaba Cloud

Importing images into Alibaba Cloud currently relies upon using a
temporary Function Compute function to work around Chinese state
censorship laws that prevent direct access to OSS bucket contents in
mainland China regions.

Unfortunately, Alibaba Cloud regions are extremely asymmetric in terms
of feature support.  (For example, some regions do not even support
IPv6 networking.)  Several mainland China regions do not support
Function Compute, and so this workaround is not available for those
regions.

A possible alternative censorship workaround is to create temporary
ECS virtual machine instances instead of temporary Function Compute
functions.  This requires the existence of a role that can be used by
ECS instances to access OSS.  We cannot use the AliyunFcDefaultRole
that is currently used by Function Compute, since this role cannot be
assumed by ECS instances.

Creating roles is a privileged operation, and it would be sensible to
assume that the image importer (which may be running as part of a
GitHub Actions workflow) may not have permission to itself create a
suitable temporary role.  The censorship bypass role must therefore be
set up once in advance by a suitably privileged user.

Add the ability to create a suitable censorship bypass role to the
Alibaba Cloud setup utility.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2026-04-14 13:59:38 +01:00
parent 0e70ad1883
commit 09c17f76c3

View File

@ -5,6 +5,7 @@ from collections import namedtuple
from concurrent.futures import ThreadPoolExecutor, as_completed
import ipaddress
from itertools import islice
import json
import time
import alibabacloud_credentials as credentials
@ -13,6 +14,9 @@ import alibabacloud_credentials.models
import alibabacloud_ecs20140526 as ecs
import alibabacloud_ecs20140526.client
import alibabacloud_ecs20140526.models
import alibabacloud_ram20150501 as ram
import alibabacloud_ram20150501.client
import alibabacloud_ram20150501.models
import alibabacloud_tea_openapi as openapi
import alibabacloud_tea_openapi.client
import alibabacloud_tea_openapi.models
@ -24,11 +28,22 @@ import alibabacloud_vpc20160428.client
import alibabacloud_vpc20160428.models
ECS_ENDPOINT = 'ecs.aliyuncs.com'
RAM_ENDPOINT = 'ram.aliyuncs.com'
IPXE_VPC_TAG = 'ipxe-default-vpc'
IPXE_VSWITCH_TAG = 'ipxe-default-vswitch'
IPXE_SG_TAG = 'ipxe-default-sg'
IPXE_CENSORSHIP_BYPASS_ROLE_NAME = 'iPXECensorshipBypassRole'
IPXE_CENSORSHIP_BYPASS_ROLE_ASSUME_POLICY = {
'Statement': [{
'Action': 'sts:AssumeRole',
'Effect': 'Allow',
'Principal': {'Service': ['ecs.aliyuncs.com']},
}],
'Version': '1',
}
Clients = namedtuple('Clients', ['region', 'ecs', 'vpc'])
def all_regions():
@ -52,6 +67,50 @@ def all_clients(region):
)
return clients
def ram_client():
"""Construct resource access management client"""
cred = credentials.client.Client()
conf = openapi.models.Config(credential=cred, endpoint=RAM_ENDPOINT)
client = ram.client.Client(conf)
return client
def setup_censorship_bypass_role(client):
"""Set up censorship bypass role (required for importing images)"""
role_name = IPXE_CENSORSHIP_BYPASS_ROLE_NAME
assume_policy = json.dumps(IPXE_CENSORSHIP_BYPASS_ROLE_ASSUME_POLICY)
req = ram.models.GetRoleRequest(
role_name=role_name,
)
try:
rsp = client.get_role(req)
arn = rsp.body.role.arn
except openapi.exceptions.ClientException as exc:
if exc.code != 'EntityNotExist.Role':
raise
req = ram.models.CreateRoleRequest(
role_name=role_name,
assume_role_policy_document=assume_policy,
)
rsp = client.create_role(req)
arn = rsp.body.role.arn
req = ram.models.UpdateRoleRequest(
role_name=role_name,
new_assume_role_policy_document=assume_policy,
new_description="iPXE role to help bypass OSS censorship restrictions",
)
rsp = client.update_role(req)
req = ram.models.AttachPolicyToRoleRequest(
role_name=role_name,
policy_type='System',
policy_name='AliyunOSSFullAccess',
)
try:
rsp = client.attach_policy_to_role(req)
except openapi.exceptions.ClientException as exc:
if exc.code != 'EntityAlreadyExists.Role.Policy':
raise
return arn
def setup_vpc(clients):
"""Set up VPC"""
tag = vpc.models.DescribeVpcsRequestTag(
@ -254,8 +313,14 @@ def setup_region(clients):
parser = argparse.ArgumentParser(description="Set up Alibaba Cloud defaults")
parser.add_argument('--region', '-r', action='append',
help="AliCloud region(s)")
parser.add_argument('--create-role', action=argparse.BooleanOptionalAction,
default=True, help="Create censorship bypass role")
args = parser.parse_args()
# Set up censorship bypass role
if args.create_role:
arn = setup_censorship_bypass_role(ram_client())
# Use all regions if none specified
if not args.region:
args.region = all_regions()
@ -271,6 +336,8 @@ with ThreadPoolExecutor(max_workers=len(args.region)) as executor:
results = {futures[x]: x.result() for x in as_completed(futures)}
# Show created resources
if args.create_role:
print("%s" % arn)
for region in args.region:
(sg_id, vpc_id, vswitch_ids) = results[region]
print("%s %s %s %s" % (region, sg_id, vpc_id, " ".join(vswitch_ids)))