Simple example of using GCP VPC-SC perimeter setup with Ingress and Egress Rules
The ingress/egress terms frequently cause confusion.
It has nothing to do with direction of data flow traffic (i.,e which way the bytes flow when copying a file in a gcs bucket) but rather context about which project is inside and outside and who is calling who:
ingress
= Project outside VPC-SC accessing project inside
egress
= Project inside VPC-SC accessing outside project
TOC:
This tutorial is a very simple setup that uses GCP VPC-SC to transfer files in a GCS bucket to and from a Project that is placed in a VPC-SC Perimeter
.
While the documentation for this feature is sufficient, this tutorial is the basic “hello world” of VPC-SC and ingress/egress rules.
This is what the tutorial shows
A) Create four projects:
(you ofcourse don’t need to create projects to follow this, just read the procedure and steps, thats all)
The specific projects and projects numbers used in the example below are:
gcs-project-332821
: 247987397047
in-perimeter-gcs
: 248336178977
ingress-vpcsc
: 715952372567
egress-vpcsc
: 1099154108957
We will also demonstrate extending the VPC-SC to a single external IP (in this case, my home’s wifi’s external IP; my laptop, essentially)
For that my fake home perimeter IP address 111.83.67.174/32
Note, in my test domain, admin@domain.com
is the Org Admin and user4@domain.com
is a regular user. We will use user4’s identity as a qualifier in the VPC-SC rule
To use this tutorial you ofcourse need to be an org admin to define the VPC-SC rules but even if you are not, you can inspect how this all works
## create environment variables
### ofcourse in your case use your own projects
export VPC_SC_PROJECT=gcs-project-332821
export VPC_SC_PROJECT_IN_PERIMETER=in-perimeter-gcs
export INGRESS_PROJECT=ingress-vpcsc
export EGRESS_PROJECT=egress-vpc
## enable services
gcloud services enable storage.googleapis.com --project $VPC_SC_PROJECT
gcloud services enable storage.googleapis.com compute.googleapis.com --project $VPC_SC_PROJECT_IN_PERIMETER
gcloud services enable storage.googleapis.com compute.googleapis.com --project $INGRESS_PROJECT
gcloud services enable storage.googleapis.com compute.googleapis.com --project $EGRESS_PROJECT
## Get project numbers
export VPC_SC_PROJECT_NUMBER=`gcloud projects describe $VPC_SC_PROJECT --format='value(projectNumber)'`
export VPC_SC_IN_PERIMETER_PROJECT_NUMBER=`gcloud projects describe $VPC_SC_PROJECT_IN_PERIMETER --format='value(projectNumber)'`
export INGRESS_PROJECT_NUMBER=`gcloud projects describe $INGRESS_PROJECT --format='value(projectNumber)'`
export EGRESS_PROJECT_NUMBER=`gcloud projects describe $EGRESS_PROJECT --format='value(projectNumber)'`
## create test buckets
gsutil mb -p $VPC_SC_PROJECT gs://$VPC_SC_PROJECT-bucket
gsutil mb -p $INGRESS_PROJECT gs://$INGRESS_PROJECT-bucket
gsutil mb -p $EGRESS_PROJECT gs://$EGRESS_PROJECT-bucket
## assign permissions to read/write to appropriate buckets
## What these steps allows is "allow user4@ permissions to read from the VPC-SC bucket
gsutil iam ch user:"user4@esodemoapp2.com":objectViewer gs://$VPC_SC_PROJECT-bucket
# this allows for the VM that will eventually be inside the perimeter to Egress to a bucket outside
gsutil iam ch serviceAccount:"$VPC_SC_IN_PERIMETER_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectAdmin gs://$EGRESS_PROJECT-bucket
## Ingress
# this allows the VM in the Ingress project to read a gcs object within the ingress project
gsutil iam ch serviceAccount:"$INGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectViewer gs://$INGRESS_PROJECT-bucket
# this allows the vm in perimeter (allows vm added into the perimeter access to write to the vpc-sc protected bucket)
gsutil iam ch serviceAccount:"$VPC_SC_IN_PERIMETER_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectViewer gs://$VPC_SC_PROJECT-bucket
# ingress rules permissions on buckets. This allows a VM in the 'ingress' project to write a file within the vpc-sc project's bucket (used for ingress vm=>bucket transfer)
gsutil iam ch serviceAccount:"$INGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectAdmin gs://$VPC_SC_PROJECT-bucket
## Egress
# egress rules permissions on buckets to read from VPC_SC_PROJECT and write to EGRESS_PROJECT_NUMBER
gsutil iam ch serviceAccount:"$EGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectViewer gs://$VPC_SC_PROJECT-bucket
gsutil iam ch serviceAccount:"$EGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com":objectAdmin gs://$EGRESS_PROJECT-bucket
## upload test files
echo -n foo > foo.txt
echo -n bar > bar.txt
gsutil cp foo.txt gs://$VPC_SC_PROJECT-bucket/foo.txt
gsutil cp bar.txt gs://$INGRESS_PROJECT-bucket/bar.txt
## set IAM policy to create a VM inside the ingress project
gcloud iam service-accounts add-iam-policy-binding \
$INGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--member=user:`gcloud config get-value core/account` \
--role='roles/iam.serviceAccountUser' --project $INGRESS_PROJECT
gcloud compute instances create ingress \
--zone=us-central1-a --machine-type=e2-medium \
--service-account=$INGRESS_PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--image=debian-11-bullseye-v20211105 --image-project=debian-cloud --project $INGRESS_PROJECT
This will extend the VPCSC boundary to a single IP address (eg, the ip of my home network)
Given the initial policy
## Create policy.
### first get the orgID
$ gcloud organizations list
DISPLAY_NAME ID DIRECTORY_CUSTOMER_ID
domain.com 673208786098 C023zw3x8
gcloud access-context-manager policies create --organization 673208786098 --title ip_trust_policy
$ gcloud access-context-manager policies list --organization 673208786098
NAME ORGANIZATION TITLE ETAG
157300459203 673208786098 ip_trust_policy f45c2de903c228d7
## create a basic policy with a fake IP address (so that we can see it fail)
gcloud access-context-manager levels create \
accessPolicies/157300459203/accessLevels/gcs_policy --title vpc_gcs_allow --basic-level-spec=basic_level.yaml
$ gcloud access-context-manager levels describe gcs_policy
basic:
conditions:
- ipSubnetworks:
- 1.2.3.4/32
name: accessPolicies/157300459203/accessLevels/gcs_policy
title: vpc_gcs_allow
$ gcloud access-context-manager levels list
NAME TITLE LEVEL_TYPE
gcs_policy vpc_gcs_allow Basic
Finally, create a perimeter using that policy
$ gcloud access-context-manager perimeters describe restrictGCS
name: accessPolicies/157300459203/servicePerimeters/restrictGCS
status:
accessLevels:
- accessPolicies/157300459203/accessLevels/gcs_policy
resources:
- projects/247987397047
restrictedServices:
- storage.googleapis.com
vpcAccessibleServices:
enableRestriction: true
title: restrictGCS
Now, on your laptop at home, try to copy a file as user4
:
$ gcloud config set account user4@domain.com
This will fail since access_context=gcs_policy
contains a fake IP ip=1.2.3.4/32
$ gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt .
AccessDeniedException: 403 SecurityPolicyViolated
>Request is prohibited by organizations policy. vpcServiceControlsUniqueIdentifier: RTSWq5KzpbOOQHU5RUmqH4ipQtRtmsuQDDT2eqIxqMWIQivq0hejHA</Details></Error>
Now update access_contenxt=gcs_policy
to contain your actual IP address. In my case, my home IP was ip=111.83.67.174/32
$ gcloud access-context-manager levels describe gcs_policy
basic:
conditions:
- ipSubnetworks:
- 111.83.67.174/32
name: accessPolicies/157300459203/accessLevels/gcs_policy
title: vpc_gcs_allow
Now your laptop is part of the VPC-SC perimeter. Try copying a file
$ gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt .
Copying gs://gcs-project-332821-bucket/foo.txt...
/ [1 files][ 3.0 B/ 3.0 B]
Operation completed over 1 objects/3.0 B.
This will add an entire project inside the perimeter and perform copy of objects within it
Now try creating a VM in project in-perimeter-gcs
. Note this project is actually outside outside the perimeter (since we haven’t done anyting yet!)
# as admin
$ gcloud compute instances create in-perimeter \
--zone=us-central1-a --machine-type=e2-medium \
--service-account=$VPC_SC_IN_PERIMETER_PROJECT_NUMBER-compute@developer.gserviceaccount.com \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--image=debian-11-bullseye-v20211105 --image-project=debian-cloud --project $VPC_SC_PROJECT_IN_PERIMETER
Remember, we already gave IAM permissions to this service account access to read a file in the VPC-SC bucket
SSH into that VM
gcloud compute ssh in-perimeter --project $VPC_SC_PROJECT_IN_PERIMETER
$ gcloud config list
[core]
account = 248336178977-compute@developer.gserviceaccount.com
project = in-perimeter-gcs
## Try to access the bucket
## this will fail because the project is not in perimeter yet
gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt .
AccessDeniedException: 403 Request is prohibited by organizations policy. vpcServiceControlsUniqueIdentifier: 9xVUHOqTLcENf1qtY7wUSJ1VlrQlCtGbDMZUbQbmFJL71WuvhxGYCw
So Now add the project into the VPC-Perimeter AND allow GCS as a within vpc accessible service:
NOTE that we removed the
access_context
rule from above just for clarity
gcloud access-context-manager perimeters describe restrictGCS --policy 157300459203
name: accessPolicies/157300459203/servicePerimeters/restrictGCS
status:
resources:
- projects/247987397047
- projects/248336178977
restrictedServices:
- storage.googleapis.com
vpcAccessibleServices:
allowedServices:
- storage.googleapis.com
enableRestriction: true
title: restrictGCS
(note the entry for vpcAccessibleServices
)
(if you did not include GCS as an vpcAccessibleServices
, you would see SERVICE_NOT_ALLOWED_FROM_VPC
, error)
Now inside the VM, try copying.. This now works because Project B is now inside the perimeter and GCS API is allowed there (so is the home IP address shown below)
gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt .
Copying gs://gcs-project-332821-bucket/foo.txt...
/ [1 files][ 3.0 B/ 3.0 B]
Operation completed over 1 objects/3.0 B.
Remember, egress rules defines a rule for a Project inside VPC-SC accessing outside project
Your a VM that we just added to the VPC-SC (the VM ), try to copy a file between buckets.
This will fails since although your laptop is inside the perimeter, the destination is not
gcloud compute ssh in-perimeter --project $VPC_SC_PROJECT_IN_PERIMETER
# then in the VM
$ gcloud config list
[core]
account = 248336178977-compute@developer.gserviceaccount.com
disable_usage_reporting = True
project = in-perimeter-gcs
$ gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt gs://$EGRESS_PROJECT-bucket
AccessDeniedException: 403 Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: JPPkuXIwWrZIJcOL_F9WfAecuT9mJ8ONU71j09uTf9jJ-KlA25nN4A
The error shows RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER
. Which means the we need to be able to copy an object FROM inside the perimeter (remember, the VM is in perimeter) to outside
So create Egress rule
gcloud access-context-manager perimeters describe restrictGCS
name: accessPolicies/157300459203/servicePerimeters/restrictGCS
status:
egressPolicies:
- egressFrom:
identities:
- serviceAccount:248336178977-compute@developer.gserviceaccount.com
egressTo:
operations:
- methodSelectors:
- method: '*'
serviceName: storage.googleapis.com
resources:
- projects/1099154108957
resources:
- projects/247987397047
- projects/248336178977
restrictedServices:
- storage.googleapis.com
vpcAccessibleServices:
allowedServices:
- storage.googleapis.com
enableRestriction: true
title: restrictGCS
That stipulates tht “serviceAccount:248336178977-compute@developer.gserviceaccount.com” (which is the VM running inside the perimeter) is allowed to egress a file from a client inside the perimeter (i.,e VPC_SC_PROJECT
) from a protected bucket TO a project projects/247987397047
(in $EGRESS_PROJECT
) that sits outside the perimeter)
So now try running the same command
$ gsutil cp gs://$VPC_SC_PROJECT-bucket/foo.txt gs://$EGRESS_PROJECT-bucket
Copying gs://gcs-project-332821-bucket/foo.txt [Content-Type=text/plain]...
/ [1 files][ 3.0 B/ 3.0 B]
Operation completed over 1 objects/3.0 B.
Finally, we will try Ingress rules. These rules allow you to copy a file from somewhere outside the VPC-SC into the secure bucket.
remember, rules define Project outside VPC-SC accessing project inside
We will attempt two variations:
A.
on a VM outside the perimeter, we will copy a file from a VM to a bucket within the VPC-SC by defining an ingress rule
B.
on a VM outside the perimeter, we will copy a file from a bucket within that project to a bucket within the VPC-SC
This will copy an object on a VM from a project outside the perimeter to inside
## first enter the VM inside the ingress project
$ gcloud compute ssh ingress --project $INGRESS_PROJECT
$ gcloud config list
[core]
account = 715952372567-compute@developer.gserviceaccount.com
disable_usage_reporting = True
project = ingress-vpcsc
## Try to copy
### this will fail since we dont' have a policy that exempts this project from the VPC-SC stipulations
$ echo -n "bar" > bar.txt
$ gsutil cp bar.txt gs://$VPC_SC_PROJECT-bucket/
AccessDeniedException: 403 Request is prohibited by organizations policy. vpcServiceControlsUniqueIdentifier: Aup5qJp7HU9va4bmDxazWo7FOGMMtPFhT4HI-Zjmtqh8PzSgmmjemw
The specific error here is NETWORK_NOT_IN_SAME_SERVICE_PERIMETER
,
So add the VPC-SC rule for ingress that allows (please note i’ve omitted the previous configurations settings from the output below)
$ gcloud access-context-manager perimeters describe restrictGCS
name: accessPolicies/157300459203/servicePerimeters/restrictGCS
status:
ingressPolicies:
- ingressFrom:
identities:
- serviceAccount:715952372567-compute@developer.gserviceaccount.com
sources:
- resource: projects/715952372567
ingressTo:
operations:
- methodSelectors:
- method: '*'
serviceName: storage.googleapis.com
resources:
- projects/247987397047
resources:
- projects/247987397047
- projects/248336178977
restrictedServices:
- storage.googleapis.com
vpcAccessibleServices:
allowedServices:
- storage.googleapis.com
enableRestriction: true
title: restrictGCS
## now the copy works
gsutil cp bar.txt gs://$VPC_SC_PROJECT-bucket/
Copying file://bar.txt [Content-Type=text/plain]...
/ [1 files][ 3.0 B/ 3.0 B]
Operation completed over 1 objects/3.0 B.
For the second variation, attempt bucket-to-bucket transfer, from inside the egress project VM (outside the perimeter)
gsutil cp gs://$INGRESS_PROJECT-bucket/bar.txt gs://$VPC_SC_PROJECT-bucket/
AccessDeniedException: 403 Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: fDcSFrzy6x63WZLBBz3C2wFXZRpPAngxasoNjJ9KKKSver8E36rMpw
hmm…i’m not sure why it did’t work…in looking at the log trace, doesn’t really say
the json is:
{
"insertId": "10x36xne3ss7a",
"logName": "projects/gcs-project-332821/logs/cloudaudit.googleapis.com%2Fpolicy",
"protoPayload": {
"@type": "type.googleapis.com/google.cloud.audit.AuditLog",
"status": {
"code": 7,
"message": "Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: fDcSFrzy6x63WZLBBz3C2wFXZRpPAngxasoNjJ9KKKSver8E36rMpw",
"details": [
{
"@type": "type.googleapis.com/google.rpc.PreconditionFailure",
"violations": [
{
"type": "VPC_SERVICE_CONTROLS",
"description": "fDcSFrzy6x63WZLBBz3C2wFXZRpPAngxasoNjJ9KKKSver8E36rMpw"
}
]
}
]
},
"authenticationInfo": {
"principalEmail": "715952372567-compute@developer.gserviceaccount.com",
"serviceAccountDelegationInfo": [
{
"firstPartyPrincipal": {
"principalEmail": "service-715952372567@compute-system.iam.gserviceaccount.com"
}
}
]
},
"requestMetadata": {
"callerIp": "35.222.50.80",
"callerNetwork": "//compute.googleapis.com/projects/ingress-vpcsc/global/networks/__unknown__",
"requestAttributes": {},
"destinationAttributes": {}
},
"serviceName": "storage.googleapis.com",
"methodName": "google.storage.objects.create",
"resourceName": "projects/247987397047",
"metadata": {
"@type": "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata",
"resourceNames": [
"projects/715952372567/buckets/ingress-vpcsc-bucket/objects/bar.txt",
"projects/247987397047/buckets/gcs-project-332821-bucket/objects/bar.txt"
],
"securityPolicyInfo": {
"organizationId": "673208786098",
"servicePerimeterName": "accessPolicies/157300459203/servicePerimeters/restrictGCS"
},
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER",
"vpcServiceControlsUniqueId": "fDcSFrzy6x63WZLBBz3C2wFXZRpPAngxasoNjJ9KKKSver8E36rMpw",
"egressViolations": [
{
"targetResource": "projects/715952372567",
"servicePerimeter": "accessPolicies/157300459203/servicePerimeters/restrictGCS",
"source": "projects/247987397047",
"sourceType": "Resource"
}
]
}
},
"receiveTimestamp": "2021-12-26T14:25:31.471317791Z",
"resource": {
"labels": {
"service": "storage.googleapis.com",
"project_id": "gcs-project-332821",
"method": "google.storage.objects.create"
},
"type": "audited_resource"
},
"severity": "ERROR",
"timestamp": "2021-12-26T14:25:30.748687635Z"
}
one thing i noticed is this line
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER",
"vpcServiceControlsUniqueId": "fDcSFrzy6x63WZLBBz3C2wFXZRpPAngxasoNjJ9KKKSver8E36rMpw",
"egressViolations": [
{
"targetResource": "projects/715952372567",
"servicePerimeter": "accessPolicies/157300459203/servicePerimeters/restrictGCS",
"source": "projects/247987397047",
"sourceType": "Resource"
}
]
it says the source project is projects/247987397047 ($VPC_SC_PROJECT)
and the target resource is in projects/715952372567 ($INGRESS_PROJECT)
…
but thats backward…the GCS bucket i’m trying to copy TO is ($VPC_SC_PROJECT)
and FROM a bucket in ($INGRESS_PROJECT)
$ gcloud projects list
PROJECT_ID NAME PROJECT_NUMBER
egress-vpc egress-vpcsc 1099154108957
gcs-project-332821 gcs-project 247987397047
in-perimeter-gcs in-perimeter-gcs 248336178977
ingress-vpcsc ingress-vpcsc 715952372567
export VPC_SC_PROJECT=gcs-project-332821
export VPC_SC_PROJECT_IN_PERIMETER=in-perimeter-gcs
export INGRESS_PROJECT=ingress-vpcsc
export EGRESS_PROJECT=egress-vpc
this is really backwards…i’m not certain why
…anyway, if i comply with the violation and setup an Egress
rule that’d allow this
$ gcloud access-context-manager perimeters describe restrictGCS
name: accessPolicies/157300459203/servicePerimeters/restrictGCS
status:
accessLevels:
- accessPolicies/157300459203/accessLevels/gcs_policy
egressPolicies:
- egressFrom:
identities:
- serviceAccount:715952372567-compute@developer.gserviceaccount.com
egressTo:
operations:
- methodSelectors:
- method: '*'
serviceName: storage.googleapis.com
resources:
- projects/715952372567
ingressPolicies:
- ingressFrom:
identities:
- serviceAccount:715952372567-compute@developer.gserviceaccount.com
sources:
- resource: projects/715952372567
ingressTo:
operations:
- methodSelectors:
- method: '*'
serviceName: storage.googleapis.com
resources:
- projects/247987397047
resources:
- projects/247987397047
restrictedServices:
- storage.googleapis.com
vpcAccessibleServices:
enableRestriction: true
title: restrictGCS
Once that last step is done, the file copy should work.
echo -n "foo" > bar.txt
gsutil cp gs://$INGRESS_PROJECT-bucket/bar.txt gs://$VPC_SC_PROJECT-bucket/
Copying file://bar.txt [Content-Type=text/plain]...
/ [1 files][ 3.0 B/ 3.0 B]
Operation completed over 1 objects/3.0 B.
again, i’m not sure why…if i find out, i’ll update this
This site supports webmentions. Send me a mention via this form.