Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Error in doc: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-prerequisites-permissions.html

Abierto Apto para principiantes
#1,241 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
74/100
Tipo de issue
Documentación
Claridad
Bien especificado
Estado de actividad
Tranquilo
Stack tecnológico
aws

Línea de trabajo

Comienza con la documentación enlazada sobre permisos de Gateway y compara su declaración de permisos de S3 con el comportamiento de caller frente a service role mostrado en la reproducción. El trabajo estará terminado cuando la página identifique correctamente qué identidad necesita s3:GetObject y ya no indique a los lectores que se lo concedan a la Gateway service role.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug documentation
Description

What the docs say:
From the Gateway permissions doc:
"If you plan to include a gateway target tool definition from an Amazon S3 URI, you'll need to include permissions for the gateway service role to access the bucket."

What is actually happening:
The caller's identity reads S3, NOT the Gateway service role.

This is an error in the doc.

Practical impact: grant s3:GetObject on the deployment role / Terraform role; no need to add it to the Gateway service role.

Steps to Reproduce

Q3_GW_ROLE_NAME = "agentcore-q3-test-gw-role"
Q3_GW_NAME = "agentcore-q3-test-gw"

trust = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
"Action": "sts:AssumeRole",
}],
}

Deliberately no s3:GetObject — only what the Gateway needs to fetch

OAuth credentials and to write logs. If the service role were the S3

reader, target creation against the OpenAPI spec would fail.

gw_policy = {
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow",
"Action": ["bedrock-agentcore:", "secretsmanager:GetSecretValue",
"logs:CreateLogGroup", "logs:CreateLogStream",
"logs:PutLogEvents"],
"Resource": "
"},
],
}
try:
iam.create_role(RoleName=Q3_GW_ROLE_NAME,
AssumeRolePolicyDocument=json.dumps(trust))
except iam.exceptions.EntityAlreadyExistsException:
pass
iam.put_role_policy(RoleName=Q3_GW_ROLE_NAME,
PolicyName="gw-inline",
PolicyDocument=json.dumps(gw_policy))
Q3_GW_ROLE_ARN = iam.get_role(RoleName=Q3_GW_ROLE_NAME)["Role"]["Arn"]
print("Gateway service role:", Q3_GW_ROLE_ARN)
print("(intentionally has NO s3:GetObject)")

Wait briefly for IAM eventual consistency before we hand the role to

create_gateway — STS AssumeRole on a freshly created role can return

AccessDenied for a few seconds.

time.sleep(10)

Drop any prior test gateway with the same name.

Both delete_gateway_target and delete_gateway are async — list_*

stops returning the resource as soon as the delete is accepted, but

create_gateway against the same name will still hit ConflictException

until the backend's internal consistency check finishes. Poll the

authoritative get_* APIs until ResourceNotFoundException.

def _drop_q3_gateway(gw_id: str, wait: int = 300) -> None:
"""Delete a gateway and wait until both its targets and the gateway
itself return ResourceNotFoundException — the only reliable signal
that name reservation has been released for re-create."""
tgt_ids: list[str] = []
try:
for tp in agentcore_ctl.get_paginator("list_gateway_targets").paginate(gatewayIdentifier=gw_id):
for t in tp.get("items", []):
tgt_ids.append(t["targetId"])
except Exception as e:
print(" warn list_gateway_targets:", e)
for tid in tgt_ids:
try:
agentcore_ctl.delete_gateway_target(gatewayIdentifier=gw_id, targetId=tid)
print(f" requested delete of target {tid}")
except Exception as e:
print(f" warn delete_gateway_target {tid}: {e}")
for tid in tgt_ids:
elapsed = 0
while elapsed < wait:
try:
agentcore_ctl.get_gateway_target(gatewayIdentifier=gw_id, targetId=tid)
except agentcore_ctl.exceptions.ResourceNotFoundException:
print(f" target {tid} fully deleted")
break
time.sleep(5)
elapsed += 5
else:
raise TimeoutError(f"target {tid} still present after {wait}s")
try:
agentcore_ctl.delete_gateway(gatewayIdentifier=gw_id)
except Exception as e:
print(" warn delete_gateway:", e)
return
elapsed = 0
while elapsed < wait:
try:
agentcore_ctl.get_gateway(gatewayIdentifier=gw_id)
except agentcore_ctl.exceptions.ResourceNotFoundException:
print(f" gateway {gw_id} fully deleted")
return
time.sleep(5)
elapsed += 5
raise TimeoutError(f"gateway {gw_id} still present after {wait}s")

for page in agentcore_ctl.get_paginator("list_gateways").paginate():
for gw in page.get("items", []):
if gw.get("name") == Q3_GW_NAME:
print(f"Found existing gateway '{Q3_GW_NAME}' ({gw['gatewayId']}); deleting before re-create")
_drop_q3_gateway(gw["gatewayId"])

Use the SERVICE app for inbound auth on this Gateway — we only need a

valid JWT to do tools/list later (data-plane re-fetch test). The

allowedClients reflects only the service app since this isn't part of

the Q4 multi-client test.

gw = agentcore_ctl.create_gateway(
name=Q3_GW_NAME,
roleArn=Q3_GW_ROLE_ARN,
protocolType="MCP",
authorizerType="CUSTOM_JWT",
authorizerConfiguration={
"customJWTAuthorizer": {
"discoveryUrl": OKTA["discovery_url"],
"allowedClients": [OKTA["service_client_id"]],
"allowedAudience": [OKTA["audience"]],
}
},
description="Q3 validation gateway",
)
Q3_GW_ID = gw["gatewayId"]
Q3_GW_URL = gw["gatewayUrl"]
print("Gateway:", Q3_GW_ID, "url:", Q3_GW_URL)

create_gateway is also async. Poll until READY before any subsequent

cell tries to attach a target — otherwise create_gateway_target races

the gateway initialization and fails with a generic backend error.

deadline = time.time() + 180
while time.time() < deadline:
info = agentcore_ctl.get_gateway(gatewayIdentifier=Q3_GW_ID)
st = info.get("status")
if st == "READY":
print("Gateway READY.")
break
if st == "FAILED":
raise RuntimeError(f"gateway {Q3_GW_ID} entered FAILED: {info.get('statusReasons')}")
time.sleep(5)
else:
raise TimeoutError(f"gateway {Q3_GW_ID} not READY within timeout")

def _create_openapi_target(name: str, key: str) -> dict:
"""Wrapper around create_gateway_target with the OpenAPI-on-S3 schema.

Pulls the spec from ``s3://{Q3_BUCKET}/{key}``. The OpenAPI target
needs an outbound credential provider — we use the dummy
apiKeyCredentialProvider created in A2 (httpbin ignores the key).
The actual outbound call to the target API is irrelevant here:
the test surface is whether ``CreateGatewayTarget`` can read the
OpenAPI spec out of the private S3 bucket.
"""
return agentcore_ctl.create_gateway_target(
    gatewayIdentifier=Q3_GW_ID,
    name=name,
    description=f"Q3 OpenAPI target for s3://{Q3_BUCKET}/{key}",
    targetConfiguration={
        "mcp": {
            "openApiSchema": {
                "s3": {"uri": f"s3://{Q3_BUCKET}/{key}"},
            }
        }
    },
    credentialProviderConfigurations=[
        {
            "credentialProviderType": "API_KEY",
            "credentialProvider": {
                "apiKeyCredentialProvider": {
                    "providerArn": Q3_APIKEY_PROVIDER_ARN,
                    "credentialLocation": "HEADER",
                    "credentialParameterName": "X-API-Key",
                }
            },
        }
    ],
)

print("→ Test 1: create target while only the caller can read S3")
try:
t1 = _create_openapi_target("q3-test1-caller-can-read", Q3_KEY_T1)
Q3_TARGET_ID_1 = t1["targetId"]
print("✅ SUCCESS — targetId:", Q3_TARGET_ID_1)
print(" → Strongly suggests the CALLER identity reads S3 (Q3b confirmed)")
except ClientError as e:
print(f"❌ FAILED: {e.response['Error']['Code']}: {e.response['Error']['Message']}")
Q3_TARGET_ID_1 = None

Trim sts:AssumedRole// back to iam::role//

for use in IAM principal matching. The current caller is likely an

assumed-role; bucket policy aws:PrincipalArn matches the role ARN,

not the session ARN.

def _principal_arn(arn: str) -> str:
if ":assumed-role/" in arn:
# arn:aws:sts::ACCT:assumed-role/RoleName/Session -> arn:aws:iam::ACCT:role/RoleName
parts = arn.split(":")
role_name = parts[5].split("/")[1]
return f"arn:aws:iam::{ACCOUNT_ID}:role/{role_name}"
return arn

CALLER_PRINCIPAL = _principal_arn(CALLER_ARN)
print("Caller principal (for bucket policy):", CALLER_PRINCIPAL)

deny_caller_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyCallerOnly",
"Effect": "Deny",
"Principal": {"AWS": CALLER_PRINCIPAL},
"Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{Q3_BUCKET}/*",
},
],
}
s3.put_bucket_policy(Bucket=Q3_BUCKET, Policy=json.dumps(deny_caller_policy))
print("Applied bucket policy: DENY GetObject for caller principal")

Allow propagation

time.sleep(10)

print("→ Test 2: create target while caller is explicitly DENIED")
try:
t2 = _create_openapi_target("q3-test2-caller-denied", Q3_KEY_T2)
Q3_TARGET_ID_2 = t2["targetId"]
print(f"❌ UNEXPECTED SUCCESS — targetId: {Q3_TARGET_ID_2}")
print(" → If S3 was actually read on this call, the GW SERVICE ROLE")
print(" must be the reader. Or AgentCore is using a privileged path.")
except ClientError as e:
print(f"✅ FAILED AS EXPECTED: {e.response['Error']['Code']}: "
f"{e.response['Error']['Message']}")
print(" → Confirms the CALLER identity reads S3")
Q3_TARGET_ID_2 = None

only_gw_role_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGatewayServiceRoleOnly",
"Effect": "Allow",
"Principal": {"AWS": Q3_GW_ROLE_ARN},
"Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{Q3_BUCKET}/",
},
{
"Sid": "DenyEveryoneElse",
"Effect": "Deny",
"NotPrincipal": {"AWS": [Q3_GW_ROLE_ARN]},
"Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{Q3_BUCKET}/
",
},
],
}
s3.put_bucket_policy(Bucket=Q3_BUCKET, Policy=json.dumps(only_gw_role_policy))
print("Applied bucket policy: only the Gateway service role may GetObject")
time.sleep(10)

But wait — the GW role itself has no s3:GetObject in its inline policy.

Bucket-policy Allow alone is insufficient for an IAM principal whose

identity policy doesn't grant the action. To make this test ONLY about

"is the GW role the reader", we also grant s3:GetObject on the role.

iam.put_role_policy(
RoleName=Q3_GW_ROLE_NAME,
PolicyName="q3-test3-temp-s3-allow",
PolicyDocument=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow", "Action": "s3:GetObject",
"Resource": f"arn:aws:s3:::{Q3_BUCKET}/*",
}],
}),
)
print("Granted s3:GetObject on the Gateway service role (inline).")
time.sleep(10)

print("→ Test 3: create target while ONLY the GW service role can read S3")
try:
t3 = _create_openapi_target("q3-test3-only-role-can-read", Q3_KEY_T3)
Q3_TARGET_ID_3 = t3["targetId"]
print(f"⚠ SUCCESS — targetId: {Q3_TARGET_ID_3}")
print(" → This would mean the GW SERVICE ROLE is the reader.")
print(" (Doc-aligned interpretation, contradicts Test 2.)")
except ClientError as e:
print(f"✅ FAILED AS EXPECTED: {e.response['Error']['Code']}: "
f"{e.response['Error']['Message']}")
print(" → Confirms the GW service role is NOT used to read S3.")
print(" Combined with Test 2, this DEFINITIVELY proves the CALLER reads S3.")
Q3_TARGET_ID_3 = None

Restore: clear bucket policy + remove the temp role policy so subsequent

tests don't carry over.

s3.delete_bucket_policy(Bucket=Q3_BUCKET)
iam.delete_role_policy(RoleName=Q3_GW_ROLE_NAME, PolicyName="q3-test3-temp-s3-allow")
print("Reverted bucket policy + temp role policy.")

Expected Behavior

"If you plan to include a gateway target tool definition from an Amazon S3 URI, you'll need to include permissions for the gateway service role to access the bucket."

Actual Behavior

The caller's identity reads S3, NOT the Gateway service role.

CLI Version

No response

Operating System

macOS

Additional Context

N/A

Lenguaje dominante
TypeScript
Estrellas
291
Forks
96
Merge medio
20 h 50 min
PR fusionados (30 d)
214

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de aws/agentcore-cli

Todos los issues de aws/agentcore-cli

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.