This guide is a walkthrough of how to leverage the
zli
and
BastionZero service
accounts
to SSH into a Linux host from Google Cloud
Run (GCR). We use the example Node.js server,
Dockerfile
, and Terraform
provided in this repo
to demonstrate this use case. Please feel free to mix and match elements of these
components with your own custom integration to better fit your specific requirements.
Note: Terraform is not required to implement this Cloud Run use case; it is included in this example repo to make the guide easier to follow.
- You must be an administrator of your BastionZero organization in order to create a BastionZero service account.
- Ensure the
zli
is installed on your machine as it is used to perform some of the one-time manual steps when creating a BastionZero service account. - Ensure
gcloud
is installed on your machine as it is used to submit a build of the example server to GCR. Don't forget to authorize thegcloud
CLI (instructions found here). - Ensure
terraform
is installed on your machine as it is used to automate some of the infrastructure required to deploy the Cloud Run service. - Ensure
docker
is installed on your machine and is currently running. - Set up Application Default Credentials (ADC) in order to configure the
google
Terraform provider used inmain.tf
:gcloud auth application-default login
. - Create a BastionZero API
key
in order to configure the
bastionzero
Terraform provider used inmain.tf
. Manage your API keys at the API key panel found here. - Clone this repository and change your current working directory (
cwd
) to the root of the repo; the shell commands in this guide assume you have changed yourcwd
accordingly.
First, we'll create a BastionZero service account in your BastionZero organization. We'll also download its associated public/private key pair and save it, along with some other credentials, in Google Secret Manager for later use in the Node.js server.
Google Cloud Platform provides a convenient way of creating the public/private key pair and the JSON Web Key Set(JWKS) URL, both of which are needed for setting up your BastionZero service account.
Please follow steps 1-4 in this guide, which details how to create the SA on GCP.
At the end of the linked guide, you should have downloaded a .json
file
(provider file) that contains your service account's credentials from GCP. Let's
rename this file to provider-file.json
, and place it in your current working
directory for convenience.
Important: Please keep note of the service account's email address as we'll
need it later. The email address should be displayed in the service account
creation screen after filling in the details. It can also be found in the table
of service accounts under "IAM" -> "Service Accounts" after creation. It should
look something like this:
<service-account-id>@<gcp-project-id>.iam.gserviceaccount.com
.
Next, let's use these credentials to create the BastionZero service account in your BastionZero organization:
zli login
zli service-account create provider-file.json
The result of the zli service-account create
command will be a
bzero-credentials.json
file created in your current working directory.
To finish up this section, we'll upload both the provider-file.json
file and
bzero-credentials.json
file as secrets in Google Secret Manager. Open up the
Secret Manager on the GCP Console.
- Click "Create Secret."
- In the name field, enter:
cloudrun-example-sa-provider-cred
. You can use a different name, but you'll have to input your chosen secret name when we apply the Terraform. - Click "Secret value" -> Upload file -> "Browse" and upload the
provider-file.json
file as the value for this secret. - Click the "Create Secret" button at the bottom to apply your selections and create the secret.
Let's create one more secret. Follow the same instructions in the section above,
except this secret's name should be cloudrun-example-sa-bzero-cred
and in step
#3, you should upload the bzero-credentials.json
file instead.
Next, we'll use the gcloud
CLI to submit a build of the example Node.js server
to GCR; our example Cloud Run service will run using this image.
Before uploading, let's explain some of the components in both the Dockerfile
and the application code (*.ts
files).
The Dockerfile
defines how to build the container image and
entrypoint for the Cloud Run service application.
The following section is the code that installs the zli
as a system package
dependency in the container. This step is required so the Node.js server can use
the zli
to SSH into a BastionZero-secured Linux host:
bastionzero-cloudrun-example/Dockerfile
Lines 6 to 12 in fa31008
This section installs the openssh-client
so that the Node.js server can
execute ssh
. It also creates an empty ~/.ssh/config
file, which the zli
updates to store config information related to connecting to your target over
SSH:
bastionzero-cloudrun-example/Dockerfile
Lines 14 to 17 in fa31008
Most of the core logic of the Node.js server can be found in
app.ts
. Let's go over some parts of the code.
We use the
@google-cloud/secret-manager
npm package to fetch and store the required service account credentials in
memory:
bastionzero-cloudrun-example/app.ts
Lines 28 to 46 in fa31008
We use zli service-account login
to perform a headless authentication to the
BastionZero platform:
bastionzero-cloudrun-example/app.ts
Lines 63 to 80 in fa31008
Using BastionZero service accounts prevents the need to perform a
user-interactive login session with your identity provider (zli login
).
We define a /ssh
HTTP endpoint that performs the SSH logic to run an arbitrary
command on a Linux host secured by BastionZero:
bastionzero-cloudrun-example/app.ts
Lines 108 to 152 in fa31008
Some steps are cached to speed-up subsequent calls to /ssh
if the same
container is still running.
zliServiceAccountLogin()
logs in to BastionZero using the service account credentials. See the previous section for more details.zli generate sshConfig
updates the~/.ssh/config
file with a list of BastionZero targets that the logged-in BastionZero service account has policy access to connect to.ssh -F ...
performs the parsed command from the query string against the Linux host via SSH.
Run the following command from the root directory of this repository to upload an image of this example application to GCR:
gcloud builds submit --tag gcr.io/<project-id>/bastionzero-cloudrun-example
Please replace <project-id>
with the GCP project ID of your choice.
With all the previous steps completed, we're now ready to perform the remaining
infrastructure tasks to get the example Cloud Run service running. This step is
easy as we'll just use the example main.tf
Terraform file to
apply these infrastructure changes.
We use the following providers in main.tf
:
- The
google
Terraform provider is used to deploy the Cloud Run service and create its service account to give the example application access to the secrets we stored earlier in Google Secret Manager. - The
docker
Terraform provider is used to find the digest hash of the image we uploaded to GCR in the previous step. - The
bastionzero
Terraform provider is used to create a target connect policy that gives the BastionZero service account access to SSH into your BastionZero targets.
Run the following command to install the providers used by main.tf
:
terraform init
The google
Terraform provider should automatically be configured if you have
set up your ADC as described in the first section.
The docker
Terraform provider has no additional configuration. However, please
ensure docker
is running before proceeding.
To configure the bastionzero
Terraform provider, you'll need to export an
environment variable that holds the API secret of your API key you created
earlier.
Set the BASTIONZERO_API_SECRET
environment variable to the API key's secret
that you created in the first section:
export BASTIONZERO_API_SECRET=api-secret
We're now ready to run terraform apply
to apply the remaining infrastructure
and deploy the Cloud Run service.
Before applying, please read over the main.tf
file and the
comments to better understand what it is doing.
Here is a quick summary:
- A new GCP service account is created. This service account is given minimal permissions, namely only read access to the secrets created earlier.
- The Cloud Run service is created. It is configured to run with the least privileged service account created in the previous step. It is also configured to run using the image we uploaded to GCR.
- A BastionZero target connect
policy
is created, which gives the BastionZero service
account SSH access (as the
root
user) to targets in theDefault
andAWS
environments in your BastionZero organization. Please modify accordingly to better fit your infrastructure requirements (e.g. use differenttarget_user
thanroot
or otherenvs
thanDefault
andAWS
).
Run the following command to apply the remaining infrastructure via Terraform:
terraform apply
Terraform will prompt you to fill in some input variables:
var.bastionzero_service_account_email
: Enter in the email of the service account we created in the beginning of this guide.var.project_id
: Enter in the same<project-id>
you selected when building the image. This same project will contain the Cloud Run service that we're about to deploy.
If you picked different secret names than the ones described
earlier, then you will also need
to override the
defaults
and pass different values for var.provider_creds_file_secret_name
and
var.bzero_creds_file_secret_name
.
Review the returned Terraform plan. Type in "yes" and press enter to apply the plan.
Let's demo the example by proxying the Cloud Run service to localhost
and
authenticating as the active account (i.e. the account you are logged in as via
gcloud
). This step is required because by default the Cloud Run service
requires authenticated access in order to invoke its public endpoints. See more
details about Cloud Run authentication
here.
Run the following command to start the proxy on localhost
:
gcloud run services proxy bzero-cloudrun
There should now be a proxy server running on localhost:8080
that proxies your
requests to the Cloud Run service.
/
: Returns the version of thezli
executable installed on the container./ssh?host=example-target
: Executes the default command against the targetexample-target
. In this example,ssh
logs in asroot
to execute the command./ssh?host=example-target&cmd=whoami
: Executes thewhoami
command against the targetexample-target
. In this example,ssh
logs in asroot
to execute the command./ssh?host=example-target&cmd=whoami&user=foo
: Executes thewhoami
command against the targetexample-target
. In this example,ssh
logs in asfoo
to execute the command. You may receive an error if the service account does not have BastionZero policy access to SSH as thefoo
user or if the userfoo
does not exist on the Linux host.
Below are some optional cleanup steps:
- Run
terraform destroy
to destroy the example Cloud Run service and other infrastructure created by Terraform. - Delete the example image
bastionzero-cloudrun-example
from your project's GCR. - Delete the secrets
cloudrun-example-sa-provider-cred
andcloudrun-example-sa-bzero-cred
from Google Secret Manager. - Disable the service account in your BastionZero organization.
- Delete the service account from GCP.
- Delete the API key in your BastionZero organization.