排查常见问题

本页列出了您在配置 VPC Service Controls 时可能会遇到的各种问题。

范围限定的政策出现意外行为

您可能会注意到,您的范围限定的政策允许一些意外的 VPC Service Controls 违规行为。如果您没有组织级层的访问权限政策,则范围限定的访问权限政策可能会遇到一些意外问题,这是一个已知问题。

如需解决此问题,请使用以下命令在组织级层创建访问权限政策:

gcloud access-context-manager policies create --organization <var>ORGANIZATION_ID</var> --title <var>POLICY_TITLE</var>

替换以下内容:

  • ORGANIZATION_ID:组织 ID。
  • POLICY_TITLE:人类可读的访问权限政策标题。

如需了解详情,请参阅创建访问权限政策

共享 VPC

使用共享 VPC 时,包含属于共享 VPC 网络的项目的服务边界还必须包含托管该网络的项目。 当属于共享 VPC 网络的项目与宿主项目不在同一个边界内时,服务可能无法正常运行或可能被完全阻止。

确保共享 VPC 网络主机与连接到该网络的项目位于同一服务边界内。

边界之间的请求

通常情况下,访问权限级别用于允许从服务边界外请求边界内受保护资源的服务边界。

但是,系统将拒绝从一个边界中的项目请求另一个边界中的受保护资源,即使访问权限级别通常允许该请求也是如此。

例如,假设边界 1 内的项目 A 请求项目 B 内的资源。项目 B 内的资源受边界 2 保护。由于项目 A 在边界内,因此即使边界 2 的访问权限级别通常允许请求受保护的资源,该请求也会被拒绝。

可以通过以下方法之一支持边界之间的请求:

  • 使用出站流量政策和入站流量政策。要允许从另一个边界向您的边界中的受保护资源发送请求,另一个边界必须使用出站流量政策,并且您必须在您的边界中设置入站政策。

  • 使用边界网桥。 网桥允许不同边界内的两个或更多个项目向这些项目中的任何服务发出请求。即使这些服务受相应边界保护,也允许这些请求。

  • 确保发出请求的服务和目标资源均不受边界的保护。在这种情况下,操作会成功,因为服务不会受到保护。

电子邮件地址无效或不存在

更新包含已删除主账号的边界时,您可能会遇到 The email address is invalid or non-existent 错误消息。

如需解决此问题,您可以执行以下任务之一:

  • 从边界中移除无效的电子邮件地址,其中包括访问权限级别以及入站和出站规则。

    您可以使用 Google Cloud 控制台或 Google Cloud CLI。

  • 如果电子邮件地址同时存在于试运行和实施边界中,则执行批量操作(仅限 Google Cloud CLI)。

    1. 获取所有边界:
      gcloud access-context-manager perimeters list --format=list \
       --policy=<POLICY_NAME> > my-perimeters.yaml
    
    1. my-perimeters.yaml 文件中移除无效的电子邮件地址并将其另存为 my-perimeters-updated.yaml

    2. 替换所有边界:

      gcloud access-context-manager perimeters replace-all --format=list \
        --source-file=my-perimeters-updated.yaml \
        --policy=<POLICY_NAME>
    

违反入站流量和出站流量规则

审核日志包含有关违反入站流量和出站流量规则的信息,可帮助您了解边界违规行为。

违反入站流量规则

违反入站流量规则表明边界外的 API 客户端尝试访问边界内的资源。由于没有匹配的入站流量规则或访问权限级别,因此服务边界会拒绝请求。

审核日志中的入站流量规则违规行为包含以下详细信息:

  • 发生入站流量规则违规行为的边界的名称。
  • 边界外的 API 客户端尝试访问的边界内资源。

在以下入站流量规则违规示例中,边界外的 API 客户端尝试访问边界 prod-perimeter 内的 Cloud Storage 存储桶 prod-protected-storage-bucket

ingressViolations: [
  0: {
    targetResource: "projects/1234/buckets/prod-protected-storage-bucket"
    servicePerimeter: "accessPolicies/123456789/servicePerimeters/prod-perimeter"
  }
]

违反出站流量规则

审核日志中的出站流量规则违规行为表明发生了以下某个事件:

  • 边界内的 API 客户端尝试访问边界外的资源。
  • 涉及边界内资源以及边界外资源的 API 请求。例如,调用一条复制命令的 Cloud Storage 客户端,其中一个存储桶位于边界内,另一个存储桶位于边界外。

由于没有匹配的出站流量规则,因此服务边界会拒绝请求。审核日志中的出站流量规则违规行为包括以下详细信息:

  • 来源类型,例如网络或资源。
  • 边界遭遇出站流量违规行为的来源(资源或网络)。
  • 遭遇出站流量违规行为的边界。
  • 请求尝试访问的边界外的目标资源。

在以下出站流量规则违规示例中,API 请求包含来自边界 prod-perimeter 内的 projects/5678 的资源,以及来自边界外的 Cloud Storage 存储桶 external-storage-bucket 的对象。

egressViolations: [
  0: {
    sourceType: "Resource"
    source: "projects/5678"
    targetResource: "projects/4321/buckets/external-storage-bucket/objects/corp-resources.json"
    servicePerimeter: "accessPolicies/123456789/servicePerimeters/prod-perimeter"
  }
]

调试被 VPC Service Controls 阻止的请求

VPC Service Controls 审核日志是用于调试 VPC Service Controls 阻止的请求的主要工具。

如果访问被意外阻止,请参阅受服务边界保护的项目中的审核日志。这些日志包含有关所请求资源的重要数据以及请求遭拒的原因。如需了解审核日志,请参阅使用问题排查工具诊断问题

以下部分列出了您在使用 VPC Service Controls 时可能会遇到的 violationReason 值。

NETWORK_NOT_IN_SAME_SERVICE_PERIMETER

此问题可能是由以下某种原因造成的:

  • 服务边界内的 VPC 网络中的客户端尝试访问不在同一边界内的项目。此请求会导致出站流量违规。如需解决此问题,请创建出站流量规则。
  • 位于服务边界外的 VPC 网络中的客户端尝试访问受该服务边界保护的项目。此请求会导致入站流量违规。如需解决此问题,请创建入站流量规则。

客户端可能会从 Compute Engine 或 Google Kubernetes Engine 虚拟机发送请求,或者通过 Cloud Interconnect 或使用 VPC 网络配置的 VPN 从本地网络发送请求。

下图显示当服务边界内的 VPC 网络中的客户端尝试访问边界外的项目时发生出站流量违规:

NETWORK_NOT_IN_SAME_SERVICE_PERIMETER 导致的出站流量违规。

以下是出站流量违规的示例:

egressViolations: [
{
  servicePerimeter: "accessPolicies/<POLICY_NAME>/servicePerimeters/<PERIMETER_NAME>"
  source: "projects/<NETWORK_PROJECT_NUMBER>"
  sourceType: "Network"
  targetResource: "projects/<RESOURCE_PROJECT_NUMBER>"
}
]

其中:

  • <POLICY_NAME> 是访问权限政策的数字名称
  • <PERIMETER_NAME> 是服务边界的名称。
  • <NETWORK_PROJECT_NUMBER> 是您的 VPC 网络所在的 Google Cloud 项目的编号。
  • <RESOURCE_PROJECT_NUMBER> 是包含资源的 Google Cloud 项目的编号。

下图显示当边界外的客户端尝试访问边界内的项目时发生入站流量违规:

NETWORK_NOT_IN_SAME_SERVICE_PERIMETER 导致的入站流量违规。

以下是入站流量违规的示例:

ingressViolations: [
{
          targetResource: "projects/<RESOURCE_PROJECT_NUMBER>",
      source: "projects/<NETWORK_PROJECT_NUMBER>"
          servicePerimeter: "accessPolicies/<POLICY_NAME>/servicePerimeters/<PERIMETER_NAME>"
}
]

其中:

  • <RESOURCE_PROJECT_NUMBER> 是包含资源的 Google Cloud 项目的编号。
  • <NETWORK_PROJECT_NUMBER> 是您的 VPC 网络所在的 Google Cloud 项目的编号。
  • <POLICY_NAME> 是访问权限政策的数字名称
  • <PERIMETER_NAME> 是服务边界的名称。

解决方法

如需解决此错误,请为边界创建入站或出站规则

RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER

如果一个请求访问多个资源,但这些资源不在同一服务边界内,则会出现此问题。无论客户端位于何处,也无论客户端是否有权访问资源,都会出现此问题。

下图显示一个客户端访问服务边界外项目和服务边界内项目的资源:

客户端访问边界外项目的资源导致的出站流量违规。

下图显示了一个客户端从两个不同服务边界内的项目访问资源,但这两个边界不相互通信:

客户端访问两个不同服务边界内项目的资源导致的出站流量违规。

以下是出站流量违规的示例:

egressViolations: [
{
  servicePerimeter: "accessPolicies/<POLICY_NAME>/servicePerimeters/<PERIMETER_NAME>"
  source: "projects/<RESOURCE_PROJECT_INSIDE_THIS_PERIMETER>"
  sourceType: "Resource"
  targetResource: "projects/<RESOURCE_PROJECT_OUTSIDE_THIS-PERIMETER>"
}
]

其中:

  • <POLICY_NAME> 是访问权限政策的数字名称
  • <PERIMETER_NAME> 是服务边界的名称。
  • <RESOURCE_PROJECT_INSIDE_THIS_PERIMETER> 是边界内的 Google Cloud 项目的编号。
  • <RESOURCE_PROJECT_OUTSIDE_THIS_PERIMETER> 是边界外的 Google Cloud 项目的编号。

解决方法

如需解决此问题,请为边界创建出站规则

NO_MATCHING_ACCESS_LEVEL

当 IP 地址、设备要求或用户身份与分配给边界的任何入站规则或访问权限级别不匹配时,就会出现此问题。这意味着不属于 Google Cloud 网络的客户端会尝试从边界外访问 Google Cloud 网络资源。例如,与审核日志的 callerIp 字段对应的 IP 地址与服务边界的访问权限级别中定义的任何 CIDR 范围不匹配。

如果调用方 IP 地址缺失或者看似内部 IP 地址,则此违规可能是未与 VPC Service Controls 集成的 Google Cloud 服务造成的。原因可能是 Google Cloud 服务尝试访问受保护的服务,但意外失败。

如需解决此问题,我们建议您创建入站流量规则而不是访问权限级别,因为入站流量规则可提供精细的访问权限控制。

下图显示了一个客户端尝试从边界外访问资源:

NO_MATCHING_ACCESS_LEVEL 导致的入站流量违规。

以下是入站流量违规的示例:

authenticationInfo: {
  principalEmail: "EMAIL"
}
requestMetadata: {
callerIp: "<PUBLIC_IP_ADDRESS>"
deviceState: "Cross Organization"
}
ingressViolations: [
        {
          targetResource: "projects/<RESOURCE_PROJECT_NUMBER>",
          servicePerimeter: "accessPolicies/<POLICY_NAME>/servicePerimeters/<PERIMETER-NAME>"
        }
  ]

其中:

  • <EMAIL> 是服务账号或经过身份验证的用户的电子邮件地址。

    如果您使用 VPC Service Controls 不支持的 Google Cloud 服务,则属于网域 google.com 的电子邮件地址会被遮盖并替换为 google-internalgoogle-internal 是指 Google 拥有的内部身份。

  • <PUBLIC_IP_ADDRESS> 是调用方的 IP 地址。对于来自互联网的调用方,这将是公共 IPv4 或 IPv6 地址。

  • <RESOURCE_PROJECT_NUMBER> 是包含资源的 Google Cloud 项目的编号。

  • <POLICY_NAME> 是访问权限政策的数字名称

  • <PERIMETER_NAME> 是服务边界的名称。

请注意,在这种情况下,metadata.accessLevels 可能仍会存在,因为可能未在违反的边界中指定这些访问权限级别。

SERVICE_NOT_ALLOWED_FROM_VPC

当客户端尝试从 VPC 网络访问 Google Cloud 资源时,会发生此问题。客户端可能会从 Compute Engine 或 Google Kubernetes Engine 虚拟机发送请求,或者通过 Cloud Interconnect 或使用 VPC 网络配置的 VPN 从本地网络发送请求。

如需解决此问题,请确保服务边界的 VPC 可访问服务配置允许被调用的服务。

场景示例

以下示例涵盖您在使用 VPC Service Controls 时可能会遇到的问题。

从本地访问 Cloud Storage

在本示例中,VPC Service Controls 会阻止从员工工作站(由 callerIp 标识)发送至 corp-storage 项目中 Cloud Storage 存储桶的请求。

该请求生成了以下审核日志记录:

{
 insertId:  "222lvajc6f7"
 logName:  "projects/corp-storage/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "someone@google.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/_"
   ]
   violationReason:  "NO_MATCHING_ACCESS_LEVEL"
  }
  methodName:  "google.storage.NoBillingOk"
  requestMetadata: {
   callerIp:  "b1d5:d26d:5b17:43fe:d358:586b:db59:9617"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/690885588241"
  serviceName:  "storage.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-27T21:40:43.823209571Z"
 resource: {
  labels: {
   method:  "google.storage.NoBillingOk"
   project_id:  "corp-storage"
   service:  "storage.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-27T21:40:42.973784140Z"
}

corp-storage 项目包含在服务边界内。员工工作站不属于该边界内的任何网络。由于员工工作站位于边界外,因此请求被阻止。

从项目外的虚拟机访问 BigQuery

在本示例中,属于项目 458854174376 (data-collector) 的一个虚拟机尝试对项目 798816221974 (corp-resources-protected) 中的一个数据集运行 BigQuery 查询,但遭到拒绝。

该虚拟机使用以下查询:

bq --project=corp-resources-protected query 'select count(*) from babynames.yob2000'

查询返回以下输出:

BigQuery error in query operation: VPC Service Controls: Request is
prohibited by organization's policy. Operation ID:
33643962-6a0f-4091-9283-bcdf7e9271f0

生成了以下审核日志记录:

{
 insertId:  "1ei551d2pdq"
 logName:  "projects/corp-resources-protected/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "714877721106-compute@example.gserviceaccount.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/1004338142803"
   ]
   violationReason:  "NETWORK_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "bigquery.googleapis.com/bigquery.jobs.create"
  requestMetadata: {
   callerIp:  "10.105.0.2"
   callerNetwork:  "//compute.googleapis.com/projects/ameet-dataflow/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/1004338142803"
  serviceName:  "bigquery.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-28T23:06:13.579882505Z"
 resource: {
  labels: {
   method:  "bigquery.googleapis.com/bigquery.jobs.create"
   project_id:  "corp-resources-protected"
   service:  "bigquery.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-28T23:06:12.799656975Z"
}

在此示例中,violationReasonNETWORK_NOT_IN_SAME_SERVICE_PERIMETER。除 callerIp 之外,还包含 callerNetwork。该 IP 地址是专用地址,并且提供了网络以消除歧义。此处涉及的相关资源列在两个位置:VpcServiceControlAuditMetadata.resourceNamesrequestMetadata.callerNetwork(拥有该网络的项目)。

问题在于,corp-resources-protected 项目在服务边界内,而 data-collector(包含虚拟机所属网络的项目)不在服务边界内。在这种情况下,访问会遭到拒绝。

跨项目 BigQuery 查询

在本示例中,属于 perimeter-network 项目的一个虚拟机尝试对两个不同项目的 BigQuery 实例进行查询:与 perimeter-network 位于同一服务边界内的 corp-resources-protected,以及位于该边界外的 corp-resources-public

该虚拟机使用以下命令:

bq query --use_legacy_sql=false \
  'select count(priv.name),count(pub.name) from \
  `corp-resources-protected.babynames.yob2000` as priv, \
  `corp-resources-public.babynames.yob2000` as pub'

查询返回以下输出:

BigQuery error in query operation: Error processing job
'example:bqjob_r211e6f6eec928ffb_000001675c996aa8_1': VPC Service Controls:
Request is prohibited by organization's policy. Operation ID:
dc4fc177-4850-4fc5-b2e7-8c33f302149a

生成了以下审核日志记录:

{
 insertId:  "17kg4exd24ag"
 logName:  "projects/perimeter-network/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/117961063178"
    1:  "projects/690885588241"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "bigquery.googleapis.com/bigquery.tables.getData"
  requestMetadata: {
   callerIp:  "130.211.225.66"
   callerNetwork:  "//compute.googleapis.com/projects/perimeter-network/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/927005422713"
  serviceName:  "bigquery.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-28T20:48:51.384237810Z"
 resource: {
  labels: {
   method:  "bigquery.googleapis.com/bigquery.tables.getData"
   project_id:  "perimeter-network"
   service:  "bigquery.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-28T20:48:50.561884949Z"
}

通过查看 callerNetworkVpcServiceControlAuditMetadata.resourceNames,我们可以看到三个项目:perimeter-network117961063178 (corp-resources-public) 和 690885588241 (corp-resources-protected)。上文已经提过,corp-resources-publicperimeter-networkcorp-resources-protected 不在同一服务边界内。

violationReasonRESOURCES_NOT_IN_SAME_SERVICE_PERIMETER,表示请求中的某个资源位于该请求所适用的边界外。在本例中,该资源是 corp-resources-public

将 Cloud Storage 文件移动到边界内

在本示例中,项目 perimeter-network 中的一个虚拟机使用命令移动文件:从位于项目 corp-resources-protected 中的一个 Cloud Storage 存储桶移动到另一个位于项目 corp-resources-public 中的存储桶。

该虚拟机使用以下命令:

gsutil mv gs://corp-resources-private-1/yob2000.txt gs://corp-resources-public-1/babynames/

该命令返回以下输出:

Copying gs://corp-resources-private-1/yob2000.txt [Content-Type=text/plain]...
AccessDeniedException: 403 Request violates VPC Service Controls.

生成了以下审核日志记录:

{
 insertId:  "1xxnssmd2hqo"
 logName:  "projects/perimeter-network/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "storage-accessing@example.iam.gserviceaccount.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/_/buckets/corp-resources-public-1"
   ]
   violationReason:  "NETWORK_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "google.storage.BillingRequiredRead"
  requestMetadata: {
   callerIp:  "130.211.225.66"
   callerNetwork:  "//compute.googleapis.com/projects/perimeter-network/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/927005422713"
  serviceName:  "storage.googleapis.com"
  status: {…}
 }
 receiveTimestamp:  "2018-11-28T00:45:31.531623485Z"
 resource: {
  labels: {
   method:  "google.storage.BillingRequiredRead"
   project_id:  "perimeter-network"
   service:  "storage.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-28T00:45:31.351140381Z"
}

在本例中,日志表述不太清楚,因为所列方法是 BillingRequiredRead,所采取的操作是 move。这是 VPC Service Controls 现有审核日志功能的局限性。

虽然原因不是很清楚,但该审核日志记录指出请求中的某个资源位于该请求所适用的边界外。在本例中,该资源是 corp-resources-public

将 Cloud Storage 文件移动到边界外

在本示例中,项目 public-network 中的一个虚拟机使用命令移动文件:从位于项目 corp-resources-protected 中的一个 Cloud Storage 存储桶移动到另一个位于项目 corp-resources-public 中的存储桶。

corp-resources-protected 项目受服务边界保护。public-networkcorp-resources-public 项目位于边界外。

该虚拟机使用以下命令:

gsutil mv gs://corp-resources-private-1/yob2000.txt gs://corp-resources-public-1/babynames/

该命令返回以下输出:

Copying gs://corp-resources-private-1/yob2000.txt [Content-Type=text/plain]...
AccessDeniedException: 403 Request violates VPC Service Controls.

生成了以下审核日志记录:

{
 insertId:  "10moqhsch9v"
 logName:  "projects/corp-resources-private/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "user@example.biz"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/_/buckets/corp-resources-private-1/objects/yob2000.txt"
    1:  "projects/_/buckets/corp-resources-public-1/objects/out.txt"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "google.storage.Write"
  requestMetadata: {
   callerIp:  "2620:15c:2c4:203:63d6:5eb8:418d:c034"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/1004338142803"
  serviceName:  "storage.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-30T16:34:46.948010626Z"
 resource: {
  labels: {
   method:  "google.storage.Write"
   project_id:  "corp-resources-private"
   service:  "storage.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-30T16:34:46.898098978Z"
}

在本示例中,审核日志指出无法跨服务边界复制数据(两个资源均在审核日志记录中)。请注意,请求源自边界外(public-network 中的虚拟机),并且其中一个存储分区位于边界外 (corp-resources-public-1)。

用户可以从边界外写入存储桶 corp-resources-public-1,因此在上一个示例中失败的检查将通过。但是,实际复制数据的后续检查会失败。

本示例演示了单个用户操作有时会引起多个内部操作,并且这些操作都必须通过实施的 VPC Service Controls。

从边界内的虚拟机复制 BigQuery 数据集

在本示例中,项目 927005422713 (perimeter-network) 中的一个虚拟机尝试将 BigQuery 数据集从项目 corp-resources-private 复制到 corp-resources-public (117961063178)。perimeter-networkcorp-resources-private 位于同一个边界内,而 corp-resources-public 位于该边界外。

该虚拟机使用以下命令:

bq cp corp-resources-private:babynames.yob2000 \
  corp-resources-public:babynames.yob2000

该命令返回以下输出:

BigQuery error in cp operation: VPC Service Controls: Request is prohibited by
organization's policy. Operation ID: c00dbc44-460f-4bd0-9d09-cda98ac800f9

生成了以下审核日志记录:

{
 insertId:  "146o5fd2hbp"
 logName:  "projects/perimeter-network/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/117961063178"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "bigquery.googleapis.com/bigquery.tables.get"
  requestMetadata: {
   callerIp:  "131.201.221.16"
   callerNetwork:  "//compute.googleapis.com/projects/perimeter-network/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/927005422713"
  serviceName:  "bigquery.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-28T00:27:05.688803777Z"
 resource: {
  labels: {
   method:  "bigquery.googleapis.com/bigquery.tables.get"
   project_id:  "perimeter-network"
   service:  "bigquery.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-28T00:27:05.378584819Z"
}

在本示例中,由于 BigQuery 日志记录机制和分布式架构的限制,没有一个底层 API 操作能够显示该请求中所有发挥作用的资源。

审核日志记录指出,操作失败的原因是 BigQuery 必须使用项目 perimeter-network(请求的来源)中的网络来访问目标项目 (corp-resources-public) 才能复制数据。请注意,corp-resources-public 位于保护 perimeter-network 的边界外。由于试图将数据泄露到 corp-resources-public,因此请求遭拒。

本示例说明,概念上的一个操作(如复制数据)可能会触发多次访问尝试,来访问不同存储系统(如 Cloud Storage、BigQuery 和 Bigtable)中的数据。根据操作的执行方式,生成的审核日志记录与原始用户命令不同。此外,在给定服务中进行多次检查并且检查可能失败时,生成的审核日志记录看起来与原始用户命令不同。

从项目中读取 Dataproc 作业

本示例显示如何调试在使用 Dataproc 等数据处理服务时出现的间接 VPC Service Controls 错误。

在本示例中,Dataproc 集群在受 VPC Service Controls 保护的项目中运行。Hello-world.py 是 pyspark 作业,尝试访问边界内 Cloud Storage 存储桶中的数据,然后将数据写入位于边界外的另一存储桶。VPC Service Controls 会阻止将数据写入边界外的存储桶的操作。

以下命令用于执行 Hello-world.py

gcloud dataproc jobs submit pyspark hello-world.py --cluster test-cluster-new2

该命令返回以下输出:

Job [50f16ca8-5102-442b-a545-eed5e4f5f5da] submitted.
Waiting for job output...
18/11/29 00:31:34 INFO org.spark_project.jetty.util.log: Logging initialized @2552ms
18/11/29 00:31:34 INFO org.spark_project.jetty.server.Server: jetty-9.3.z-SNAPSHOT
18/11/29 00:31:34 INFO org.spark_project.jetty.server.Server: Started @2640ms
18/11/29 00:31:34 INFO org.spark_project.jetty.server.AbstractConnector: Started ServerConnector@1f1c18ec{HTTP/1.1,[http/1.1]}{0.0.0.0:4040}
18/11/29 00:31:34 INFO com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystemBase: GHFS version: 1.6.4-hadoop2
18/11/29 00:31:35 INFO org.apache.hadoop.yarn.client.RMProxy: Connecting to ResourceManager at test-cluster-new2-m/10.246.0.3:8032
18/11/29 00:31:37 INFO org.apache.hadoop.yarn.client.api.impl.YarnClientImpl: Submitted application application_1522454176466_0005
Traceback (most recent call last):
  File "/tmp/50f16ca8-5102-442b-a545-eed5e4f5f5da/hello-world.py", line 8, in <module>
    lear.saveAsTextFile("gs://corp-resources-public-1/out.txt")
  File "/usr/lib/spark/python/lib/pyspark.zip/pyspark/rdd.py", line 1553, in saveAsTextFile
  File "/usr/lib/spark/python/lib/py4j-0.10.4-src.zip/py4j/java_gateway.py", line 1133, in __call__
  File "/usr/lib/spark/python/lib/py4j-0.10.4-src.zip/py4j/protocol.py", line 319, in get_return_value
py4j.protocol.Py4JJavaError: An error occurred while calling o49.saveAsTextFile.
: java.io.IOException: Error accessing: bucket: corp-resources-public-1, object: out.txt
    at com.google.cloud.hadoop.gcsio.GoogleCloudStorageImpl.wrapException(GoogleCloudStorageImpl.java:1767)
$sp(PairRDDFunctions.scala:961)

 (truncated)

Caused by: com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
  "code" : 403,
  "errors" : [ {
    "domain" : "global",
    "message" : "Request violates VPC Service Controls.",
    "reason" : "vpcServiceControls"
  } ],
  "message" : "Request violates VPC Service Controls."
}
    at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)

 (truncated)

18/11/29 00:31:43 INFO org.spark_project.jetty.server.AbstractConnector: Stopped Spark@1f1c18ec{HTTP/1.1,[http/1.1]}{0.0.0.0:4040}
ERROR: (gcloud.dataproc.jobs.submit.pyspark) Job [50f16ca8-5102-442b-a545-eed5e4f5f5da] entered state [ERROR] while waiting for [DONE].

请注意调用 saveAsTextFile 方法时发生的 IO 异常。Cloud Storage 返回 Request violates VPC Service Controls 错误并显示消息 403。该错误指出必须检查 Cloud Storage 审核日志操作。

在执行命令的 perimeter-network 项目的审核日志中,有一条对应于 saveAsTextFile 操作的审核日志记录:

{
 insertId:  "qdj1o9d1run"
 logName:  "projects/corp-resources-private/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "1004338142803-compute@example.gserviceaccount.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/_/buckets/corp-resources-public-1/objects/out.txt"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "google.storage.BillingRequiredRead"
  requestMetadata: {
   callerIp:  "10.246.0.3"
   callerNetwork:  "//compute.googleapis.com/projects/corp-resources-private/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/1004338142803"
  serviceName:  "storage.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-29T00:31:43.666227930Z"
 resource: {
  labels: {
   method:  "google.storage.BillingRequiredRead"
   project_id:  "corp-resources-private"
   service:  "storage.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-29T00:31:43.608250320Z"
}

由于审核日志限制,Cloud Storage 的 methodName 被列为 Read,即便它实际为 write 操作。审核日志记录指出,操作失败是由于项目 corp-resources-private 中的网络尝试访问存储桶 corp-resources-public-1 中某个资源的数据(在本例中为写入)。由于 Cloud Storage 审核日志的限制,尚不清楚存储桶 corp-resources-public-1 属于哪个项目。

如需识别包含 corp-resources-public-1 的项目,请使用以下命令:

gsutil --debug ls -L -b gs://corp-resources-public-1 2>&1 | grep projectNumber

该命令返回以下输出:

projectNumber: u'117961063178'

117961063178 是位于边界外的项目 corp-resources-public。因此,失败符合预期。

由于服务不受支持而出错

一些 Google Cloud 服务的实施依赖于其他 Google Cloud 服务。如果在受边界保护的项目中使用不支持的服务(如 App Engine),则服务的资源可能无法访问。

如需了解已知有问题的情况,请参阅已知服务限制

带有受限 VIP 的不支持的服务

尝试访问 VPC Service Controls 受限 VIP 不支持的 API 会导致 403 错误。例如,VPC Service Controls 不支持 App Engine,因此在使用受限 VIP 时,App Engine Admin API 不可用。

例如,假设使用以下命令列出服务边界内的所有 App Engine 服务:

gcloud app services list

该命令返回以下输出:

ERROR: (gcloud.app.services.list) User [***] does not have permission to access apps instance [***] (or it may not exist): <!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 403 (Forbidden)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){ #logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){ #logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>403.</b> <ins>That's an error.</ins>
  <p>Your client does not have permission to get URL <code>/v1/apps/***/services</code> from this server.  <ins>That's all we know.</ins>

对于 VPC Service Controls 不支持且在受限 VIP 上不可用的服务,预计会出现此类型错误。如果 VPC Service Controls 支持的某个服务出现此错误,则我们建议您检查该服务的已知服务限制,以查看这是否为已知限制。如果不是已知限制,则必须报告此问题

日志导出到边界外的项目

在本示例中,VPC Service Controls 阻止日志导出。导出目标位置(项目 corp-resources-public)位于 VPC Service Controls 边界外,而接收器创建于边界内的项目 perimeter-network 上。

例如,假设使用以下命令:

gcloud logging sinks describe example-sink

该命令返回以下输出:

destination: bigquery.googleapis.com/projects/corp-resources-public/datasets/logs
filter: |-
  resource.type="audited_resource"
  resource.labels.service="bigquery.googleapis.com"
name: example-sink
outputVersionFormat: V2
writerIdentity: serviceAccount:p927005422713-439672@gcp-sa-logging.iam.gserviceaccount.com

生成了以下审核日志记录:

{
 insertId:  "e5i2i8cbqw"
 logName:  "projects/perimeter-network/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "p927005422713-439672@gcp-sa-logging.iam.gserviceaccount.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "corp-resources-public"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "google.cloud.bigquery.v2.TableDataService.InsertAll"
  requestMetadata: {
   callerIp:  "2002:a49:8c51::"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/927005422713"
  serviceName:  "bigquery.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-11-29T17:32:19.287138882Z"
 resource: {
  labels: {
   method:  "google.cloud.bigquery.v2.TableDataService.InsertAll"
   project_id:  "perimeter-network"
   service:  "bigquery.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-11-29T17:32:19.054662413Z"
}

审核日志记录是针对 BigQuery 生成的,而不是针对 Logging。这是因为 BigQuery 是 Logging 尝试写入的接收器服务。

导出失败是因为 corp-resources-public 位于保护 perimeter-network 的边界外。

本示例显示,如果一个 Google Cloud 服务使用 Google Cloud 内部的代管式服务账号(如 p927005422713-439672@gcp-sa-logging.iam.gserviceaccount.com)来调用另一个 Google Cloud 服务,则该请求的“网络项目”(在本例中为 perimeter-network)将从该身份推导。同一身份代表日志导出资源本身。

这种模式在 Google Cloud 中很常见,适用于服务间交互的众多情况。

BigQuery 提取到 Cloud Storage

本示例介绍如何调试 BigQuery 提取到 Cloud Storage 时失败的情况。

在本示例中,corp-resources-privateperimeter-network 是受服务边界保护的项目。corp-resources-public 是位于边界外的项目。

假设使用以下命令:

bq extract babynames.yob2000

该命令返回以下输出:

gs://corp-resources-public-1/export.txt
Waiting on bqjob_r47ee34109d02b41_000001676b27157c_1 ... (1s) Current status: DONE
BigQuery error in extract operation: Error processing job 'corp-resources-private:bqjob_r47ee34109d02b41_000001676b27157c_1': Access
Denied: BigQuery BigQuery: Permission denied while writing data.

在本例中,错误并未明确指出涉及 VPC Service Controls。如果发生 Identity and Access Management 故障,则系统会显示类似错误。

生成了以下审核日志记录:

{
 insertId:  "4gbh6pe8jld7"
 logName:  "projects/corp-resources-private/logs/cloudaudit.googleapis.com%2Fdata_access"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "storage-accessing@example.iam.gserviceaccount.com"
  }
  methodName:  "jobservice.jobcompleted"
  requestMetadata: {
   callerIp:  "10.5.0.4"
   callerNetwork:  "//compute.googleapis.com/projects/perimeter-network/global/networks/__unknown__"
   callerSuppliedUserAgent:  "google-api-python-client/1.6.5 (gzip),gzip(gfe)"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/corp-resources-private/jobs/bqjob_r47ee34109d02b41_000001676b27157c_1"
  serviceData: {
   @type:  "type.googleapis.com/google.cloud.bigquery.logging.v1.AuditData"
   jobCompletedEvent: {
    eventName:  "extract_job_completed"
    job: {
     jobConfiguration: {
      extract: {
       destinationUris: [
        0:  "gs://corp-resources-public-1/export.txt"
       ]
       sourceTable: {
        datasetId:  "babynames"
        projectId:  "corp-resources-private"
        tableId:  "yob2000"
       }
      }
     }
     jobName: {
      jobId:  "bqjob_r47ee34109d02b41_000001676b27157c_1"
      location:  "US"
      projectId:  "corp-resources-private"
     }
     jobStatistics: {
      createTime:  "2018-12-01T19:03:03.908Z"
      endTime:  "2018-12-01T19:03:05.494Z"
      startTime:  "2018-12-01T19:03:04.013Z"
     }
     jobStatus: {
      additionalErrors: [
       0: {
        code:  7
        message:  "Access Denied: BigQuery BigQuery: Permission denied while writing data."
       }
      ]
      error: {
       code:  7
       message:  "Access Denied: BigQuery BigQuery: Permission denied while writing data."
      }
      state:  "DONE"
     }
    }
   }
  }
  serviceName:  "bigquery.googleapis.com"
  status: {
   code:  7
   message:  "Access Denied: BigQuery BigQuery: Permission denied while writing data."
  }
 }
 receiveTimestamp:  "2018-12-01T19:03:05.532169998Z"
 resource: {
  labels: {
   project_id:  "corp-resources-private"
  }
  type:  "bigquery_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-12-01T19:03:05.503Z"
}

在该审核日志记录中,storage-accessing@example.iam.gserviceaccount.com 被标识为尝试运行操作的身份。在本示例中,假设 storage-accessing@example.iam.gserviceaccount.com 具有执行命令所需的 IAM 权限。

由于 IAM 权限不是问题,因此下一步是检查 VPC Service Controls 失败。

目标位置服务 (Cloud Storage) 的审核日志记录包含详细的失败原因:

{
 insertId:  "1bq397kcfj1"
 logName:  "projects/corp-resources-private/logs/cloudaudit.googleapis.com%2Fpolicy"
 protoPayload: {
  @type:  "type.googleapis.com/google.cloud.audit.AuditLog"
  authenticationInfo: {
   principalEmail:  "storage-accessing@example.iam.gserviceaccount.com"
  }
  metadata: {
   @type:  "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
   resourceNames: [
    0:  "projects/1004338142803"
    1:  "projects/_/buckets/corp-resources-public-1"
   ]
   violationReason:  "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"
  }
  methodName:  "google.storage.BillingRequiredRead"
  requestMetadata: {
   callerIp:  "10.5.0.4"
   callerNetwork:  "//compute.googleapis.com/projects/perimeter-network/global/networks/__unknown__"
   destinationAttributes: {
   }
   requestAttributes: {
   }
  }
  resourceName:  "projects/1004338142803"
  serviceName:  "storage.googleapis.com"
  status: {
   code:  7
   details: [
    0: {
     @type:  "type.googleapis.com/google.rpc.PreconditionFailure"
     violations: [
      0: {
       type:  "VPC_SERVICE_CONTROLS"
      }
     ]
    }
   ]
   message:  "Request is prohibited by organization's policy"
  }
 }
 receiveTimestamp:  "2018-12-01T19:03:05.617451586Z"
 resource: {
  labels: {
   method:  "google.storage.BillingRequiredRead"
   project_id:  "corp-resources-private"
   service:  "storage.googleapis.com"
  }
  type:  "audited_resource"
 }
 severity:  "ERROR"
 timestamp:  "2018-12-01T19:03:05.420005215Z"
}

在该日志中,很明显,为了完成命令同时使用了 1004338142803 (corp-resources-private-1) 和 corp-resources-public 这两个项目。由于这些项目不在同一边界内,因此提取作业失败。

本示例说明,在复杂的多服务操作中,来源服务和目标位置服务的审核日志都有可能包含有用的调试数据。

后续步骤