Authentication Google Cloud Platform (GCP)

DevOps Secrets Vault provides two ways to authenticate using GCP. One is through a Google service account and the other is through Google Compute Engine (GCE) metadata.

Google Service Account Authentication

To setup GCP authentication using service accounts in DSV, a GCP service account must be provided that DSV can use as the authentication provider. This service account must be assigned to the project you are working in, have the role Service Account Key Admin so that it can issue and manage service account tokens, and a key must be generated.

These steps can be done programmatically, but we will use the GCP Console.

GCP Service Account Setup

In the GCP Console Home page, go to your project, hover IAM & Admin, and then click Service Accounts.

At the top, click CREATE SERVICE ACCOUNT.

For the first step, enter an account name. We will use dsv-svc in this example. Click CREATE.

In the second step, click the drop-down arrow in the Select a role box, type service account key admin in the filter and select Service Account Key Admin. Then click Continue.

In the third step, click CREATE KEY and when the option to generate a file slides in from the right, select json and click CREATE. A file will be downloaded that will have all the information needed to setup the DSV authentication provider.

The Goolge API for IAM must be enabled. To do this in the Google Console, go to the relevant project and on the left navigation, hover APIs & Services then select Library.

In the search, type Identity and Access and in the results, select the Identity and Access Management (IAM) API. Click Enable.

DSV Authentication Provider Setup

Go back to the terminal (DevOps Secrets Vault CLI).

Use dsv config auth-provider search -e yaml to see all of your current authentication providers.

Initially, the only authentication provider is Thycotic One, similar to this:

Copy
created: "2019-11-11T20:29:20Z"
createdBy: users:thy-one:admin@company.com
id: xxxxxxxxxxxxxxxxxxxx
lastModified: "2020-05-18T03:58:15Z"
lastModifiedBy: users:thy-one:admin@company.com
name: thy-one
properties:
 baseUri: https://login.thycotic.com/
 clientId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 clientSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
type: thycoticone
version: "0"

Setup the DSV authentication provider. Create a json file named auth-gcp.txt with the following format, substituting the dsv-svc service account values in the key file you downloaded from the GCP console.

Copy
{
  "name": "gcloud",
  "type": "gcp",
  "properties": {
    "ProjectId": "{project-id}",
    "type": "service_account",
    "PrivateKeyId": "{private-key-id}",
    "PrivateKey": "-----BEGIN PRIVATE KEY-----{private-key}-----END PRIVATE KEY-----\n",
    "ClientEmail": "{clientemail}",
    "TokenURI": "https://oauth2.googleapis.com/token"
  }
}

In the DSV CLI, run dsv config auth-provider create --data @auth-gcp.txt to create the GCP authentication provider.

dsv config auth-provider <name> read -e yaml where the example name we will use here is gcloud

Copy
created: "2019-11-12T18:34:49Z"
createdBy: users:thy-one:admin@company.com
id: bq4ce17cj2bc72qun8vg
lastModified: "2020-05-18T03:58:15Z"
lastModifiedBy: users:thy-one:admin@company.com
name: gcloud
properties:
clientEmail: dsv-svc@myfirstproject-xxxxxx.iam.gserviceaccount.com
privateKey: |
-----BEGIN PRIVATE KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXX=
-----END PRIVATE KEY-----
privateKeyId: 9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3
projectId: myfirstproject-xxxxxx
tokenUri: https://oauth2.googleapis.com/token
type: service_account
type: gcp

DSV Service Account/User Mapping

Now the service account that is going to access DSV is required. For this example, we will name this account client-svc The setup in GCP is the same as above for the dsv-svc account except that when the role is assigned, it must be Service Account Token Creator so that this account can request tokens. Also, after generating the key, make sure to save the file to the local machine that will access DSV and note the location.

In the DSV CLI, create a User called gcp-test referring to the client-svc service account with gcloud as the authentication provider using dsv user create --username gcp-test --provider gcloud --external-id client-svc@myfirstproject-273119.iam.gserviceaccount.com.

Copy
{
  "cursor": "",
  "data": [
    {
      "created": "2020-04-04T17:56:33Z",
      "createdBy": "users:thy-one:admin@company.com",
      "externalId": "client-svc@myfirstproject-xxxxxx.iam.gserviceaccount.com",
      "id": "d6a8e1e5-5554-4fc8-a4ca-1c1a653f9095",
      "lastModified": "2020-04-04T17:56:33Z",
      "lastModifiedBy": "users:thy-one:admin@company.com",
      "provider": "gcloud",
      "userName": "gcp-test",
      "version": "0"
    }
  ],
  "length": 1,
  "limit": 25
}

Set an environmental variable named GOOGLE_APPLICATION_CREDENTIALS to the path of the key file for client-svc that was just downloaded.

In Linux or Mac, this might look like:

Copy
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Windows Powershell

Copy
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\Users\username\Downloads\[FILE_NAME].json"

Windows Command Line

Copy
set GOOGLE_APPLICATION_CREDENTIALS=C:\Users\username\Downloads\[FILE_NAME].json

After creating the User, modify the config to give that User access to the default administrator permission policy.

Adding a User to the admin policy is not security best practices. This is for example purposes only. Ideally, you would create a separate policy for this GCP service account with restricted access. For details on limiting access through policies, see the Policy section.

Copy
dsv config edit

Add gcloud:gcp-test as a User to the Default Admin Policy. Third party accounts must be prefixed with the provider name; in this case, the fully qualified username would be glcoud:gcp-test.

Copy
<snip>
- actions:
 - <.*>
 conditions: {}
 description: Default Admin Policy
 effect: allow
 id: xxxxxxxxxxxxxxxxxxxx
 meta: null
 resources:
 - <.*>
 subjects:
 - users:<gcloud:gcp-test|admin@company.com>
<snip>

Run dsv init filling out the desired values and selecting 6 GCP (federated) when prompted for the auth type.

Copy
Please enter auth type:
       (1) Password (local user)(default)
       (2) Client Credential
       (3) #{ThycoticOne}# (federated)
       (4) AWS IAM (federated)
       (5) Azure (federated)
       (6) GCP (federated)
       (7) OIDC (federated)

Run dsv auth to verify authentication. A token will be displayed.

Run dsv secret read <path to any secret> to verify secret access.

Google Compute Engine (GCE) Metadata Authentication

The idea behind GCE Metadata authentication is to enable a GCE instance to gain access to DevOps Secrets Vault.

In this example we assume you have created a Linux Google Compute Instance and have the Google Compute Engine API enabled.

It is further assumed that the Compute Engine default service account is used. However, you can assign a different service account to the Compute instance if desired.

Using the GCE default service account is generally not best practices because it is defaulted to every GCE that is created, violating the idea of least privileges. This is for illustration purposes.

To find the Compute Engine default service account email, from the GCP Console Home, hover IAM and then click Service Accounts.

The name will say "Compute Engine default service account". Copy and store the email for later.

DSV GCE Authentication Provider setup

Using any computer with Admin DSV access, we now want to setup the DSV Authentication Provider.

Create a file named 'auth-gcp.txt' in the following format and substituting your ProjectID.

Copy
{
  "name": "gcloud-gce",
  "type": "gcp",
  "properties": {
    "ProjectId": "myfirstproject-273119"
  }
}

Run dsv config auth-provider create --data @auth-gcp.txt to implement the Authentication Provider.

To view the resulting addition to the config file, you would use:

dsv config auth-provider <name> read -e yaml where the example name we will use here is gcloud-gce.

Copy

- ID: bq71e5co19js72ppv140
name: gcloud-gce
properties:
projectId: myfirstproject-273119
type: gcp
tenantName: company
Copy
created: "2019-11-12T18:34:49Z"
createdBy: users:thy-one:admin@company.com
id: xxxxxxxxxxxxxxxxxxxx
lastModified: "2020-05-18T03:58:15Z"
lastModifiedBy: users:thy-one:admin@company.com
name: gcloud-gce
properties:
projectId: myfirstproject-xxxxxx
type: gcp
version: "0"

DSV GCE Metadata Service Account/DSV User Mapping

Run dsv user create --username gce-test --provider gcloud-gce --external-id {default compute service account email} using the default service account email we saved earlier.

Copy
{
  "created": "2020-04-09T12:59:44Z",
  "createdBy": "users:thy-one:admin@company.com",
  "externalId": "2XXXXXXXXXX3-compute@developer.gserviceaccount.com",
  "id": "19709b4e-2a13-4164-a930-81997b568036",
  "lastModified": "2020-04-09T12:59:44Z",
  "lastModifiedBy": "users:thy-one:admin@company.com",
  "provider": "gcloud-gce",
  "userName": "gce-test",
  "version": "0"
}

After creating the User, modify the config to give that User access to the default administrator permission policy.

Adding a user to the admin policy is not security best practices. This is for example purposes only. Ideally, you would create a separate policy for this GCP service account with restricted access. For details on limiting access through policies, see the Policy section.

Copy
dsv config edit

Add gcloud:gce-test as a user to the Default Admin Policy. Third-party accounts must be prefixed with the provider name; in this case, the fully qualified username would be glcoud-gce:gce-test.

Adding a user to the admin policy is not security best practices. This is for example purposes only. Ideally, you would create a separate policy for this AWS user with restricted access. For details on limiting access through policies, see the Policy section.

Copy
dsv config edit -e yaml
Copy
<snip>
- actions:
 - <.*>
 conditions: {}
 description: Default Admin Policy
 effect: allow
 id: xxxxxxxxxxxxxxxxxxxx
 meta: null
 resources:
 - <.*>
 subjects:
 - users:<gcloud-gce:gce-test|admin@company.com>
<snip>

GCE Authentication

SSH into the GCE and download the latest DSV CLI from this website DSV CLI.

For example, curl: https://dsv.secretsvaultcloud.com/downloads/cli/1.31.0/dsv-linux-x64 -o dsv

You may need to give yourself permissions to run the DSV binary, and it is also easier if you set the path.

Run dsv init filling out the desired values and selecting 6 GCP (federated) when prompted for the auth type.

Copy
Please enter auth type:
       (1) Password (local user)(default)
       (2) Client Credential
       (3) #{ThycoticOne}# (federated)
       (4) AWS IAM (federated)
       (5) Azure (federated)
       (6) GCP (federated)
       (7) OIDC (Federated)

Run dsv auth to verify authentication. A token will be displayed.

Run dsv secret read <path to any secret> to verify secret access.

Google Kubernetes Engine (GKE) Authentication

It follows that if you can have a GCE (aka a virtual server) authenticate to DSV, that there would be a similar way to do that with a Google Kubernetes Engine (GKE) node.

Here is an example where we deploy a simple app in GKE that is able to authenticate to DSV.

In the GCE example above, we used the Compute Engine default service account. Here we suggest you create a service account with at least the storage.objectViewer role for the project which will enable the ability to pull an image from GCP registry. In this example, we created a service account named dsv-gce.

DSV Authentication provider

Using any computer with Admin DSV access, setup the DSV Authentication Provider.

Create a file named 'auth-gcp.txt' in the following format and substituting your GCP <ProjectID>.

Copy
{
  "name": "gcloud-gce",
  "type": "gcp",
  "properties": {
    "ProjectId": "myfirstproject-273119"
  }
}

Run dsv config auth-provider create --data @auth-gcp.txt to implement the Authentication Provider.

DSV User mapped to the GKE service account

Run dsv user create --username gce-test --provider gcloud-gce --external-id {dsv-gce service account email} using the default service account email we saved earlier. You will get a response like this.

Copy
{
  "created": "2020-04-09T12:59:44Z",
  "createdBy": "users:thy-one:admin@company.com",
  "externalId": "dsv-gce@gcp-project-id.iam.gserviceaccount.com",
  "id": "19709b4e-2a13-4164-a930-81997b568036",
  "lastModified": "2020-04-09T12:59:44Z",
  "lastModifiedBy": "users:thy-one:admin@company.com",
  "provider": "gcloud-gce",
  "userName": "gce-test",
  "version": "0"
}

Back to GCP to setup a GKE cluster

From the GCP Home page, in the left menu, hover over Kubernetes Engine and select Clusters. Then Create Cluster. If this is the first one, then GCP will enable the GKE API for you.

When the form comes up, the default values can be used with the exception of the service account. To change this, in the left navigation, select default-pool then Security where you will select the service account dsv-gce just mentioned.

Click Create. It takes a few minutes for the cluster to be built.

Hello-App

Now create and deploy this Go-based hello app in this cluster node.

We will use the built-in GCP Cloud shell to connect since it comes with Docker, Kubectl, and connectivity to GCP all setup. It even has a nice editor for the files we will create. To do this, go to the Kubernetes Engine then Clusters page. From the list, there is a Connect button that opens a modal pop-up. In the modal, select Run in Cloud Shell.

A terminal opens in the browser. Run the following steps.

Copy
mkdir hello-app
cd hello-app
cat >  main.go

Now you can copy the code below into the terminal, but substitute the tenant_url to your URL, which will look something like https://mycompany.secretsvaultcloud.com.

Copy
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", hello)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    log.Printf("Server listening on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, mux))
}

func hello(w http.ResponseWriter, r *http.Request) {
    log.Printf("Serving request: %s", r.URL.Path)

    fmt.Println("-----------computeMetadata-----------")

    client := &http.Client{}
    req, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/project/project-id", nil)
    if err != nil{
        fmt.Fprintf(w, "Error creating Metadata Request: %s\n", err.Error())
        return
    }
    req.Header.Add("Metadata-Flavor", Google)
    resp, err := client.Do(req)
    if err != nil{
        fmt.Fprintf(w, "Error creating Metadata : %s\n", err.Error())
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil{
        fmt.Fprintf(w, "Error parsing body computeMetadata: %s\n",  err.Error())
        return
    }
    fmt.Fprintf(w, "Response computeMetadata: %s\n", string(body))

    fmt.Println("-----------computeMetadata-service-accounts-----------")

    tenant_url := "{tenant url}"
    client2 := &http.Client{
    }
    req2, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity", nil)
    if err != nil{
        fmt.Fprintf(w, "Error creating service-accounts Metadata Request: %s\n", err.Error())
        return
    }
    req2.Header.Add("Metadata-Flavor", Google)
    q := req2.URL.Query()
    q.Add("audience", tenant_url)
    q.Add("format", "full")
    req2.URL.RawQuery = q.Encode()
    resp2, err := client2.Do(req2)
    if err != nil{
        fmt.Fprintf(w, "Error creating service-accounts Metadata : %s\n", err.Error())
        return
    }

    body2, err := ioutil.ReadAll(resp2.Body)
    if err != nil{
        fmt.Fprintf(w, "Error parsing body service-accounts computeMetadata: %s\n",  err.Error())
        return
    }
    fmt.Fprintf(w, "Response service-accounts computeMetadata: %s\n", string(body2))

    fmt.Println("-----------DSV-----------")

    reqBody, _ := json.Marshal(map[string]string{
        "grant_type" : "gcp",
        "jwt" : string(body2),
    })

    dsvResp, err := http.Post(tenant_url+"/v1/token","application/json", bytes.NewBuffer(reqBody))
    if err != nil || dsvResp == nil{
        if err!= nil {
            fmt.Fprintf(w, "Error creating dsv Request: %s\n", err.Error())
        }
        return
    }

    dsvBody, err := ioutil.ReadAll(dsvResp.Body)
    if err != nil{
        fmt.Fprintf(w, "Error parsing body dsv: %s\n",  err.Error())
    } else{
        fmt.Fprintf(w, "Response from DSV: %s\n", string(dsvBody))
    }
}

Use ctrl+c to escape out.

Now create the docker file.

Copy
cat > Dockerfile

Copy the commands below in.

Copy
FROM golang:1.13-alpine
ADD . /go/src/hello-app
RUN go install hello-app

FROM alpine:latest
COPY --from=0 /go/bin/hello-app .
ENV PORT 8080
CMD ["./hello-app"]

Use ctrl+c to escape out.

Run these commands to build and push the app to GKE. Substitute your project ID in.

Copy
docker build -t gcr.io/{PROJECT_ID}/hello-app:v1 .   
docker push gcr.io/{PROJECT_ID}/hello-app:v1

The docker image is in GCP registry, so now create the Kubernetes deployment.

Copy
cat > k8.yml

Substitute your project id and paste the following.

Copy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      name: my-app
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: gcr.io/{PROJECT_ID}/hello-app:v1
          volumeMounts:
            - name: certs
              mountPath: /etc/ssl/certs
      volumes:
        - name: certs
          hostPath:
            path: /etc/ssl/certs

Use ctrl+c to escape out.

And deploy:

Copy
kubectl apply -f k8.yml

Make sure the pod is in running status.

Copy
kubectl get pod

Now expose the app to the internet.

Copy
kubectl expose deployment my-app --type=LoadBalancer --port 80 --target-port 8080
kubectl get service

You should see:

It will take a few minutes for the <pending> to turn to an IP address.

Retry kubectl get service until you see IP address in EXTERNAL-IP.

Copy the EXTERNAL-IP for my-app and paste in your browser. You should a get DSV token.

At this point you are successfully logged into DSV from GKE. There are two tokens, the first one is the GKE metadata token. The second one is the DSV authentication token. If you parse the DSV token at the jwt.io website you should see the username gcloud-gce:gce-test to confirm.