Skip to content

Install on Google Cloud

This page is the Google Cloud variant of Install on Kubernetes. The Authwise operator deploys the same workloads on Google Kubernetes Engine (GKE) as on any other cluster. What changes is where the database, the keys, and the uploaded assets live, and how pods get a Google Cloud identity. Read the Kubernetes page first; the sections here link to it rather than repeat it.

An install on Google Cloud runs its platform service in one of two ways:

  • platform-hosted, deployed by the operator. Keys live in a keyset Secret you generate, and assets live on a persistent disk. This is the shape the Kubernetes page describes, and it is the recommended starting point.
  • platform-gcp, deployed by you. Cloud KMS wraps the keys, a Cloud Storage bucket holds the assets, and the service authenticates with Workload Identity Federation for GKE, so no key material or credential is stored in the cluster. The operator has no gcp block: it does not deploy platform-gcp. You run it as a Deployment in the install’s namespace and point the install at it with spec.platform.enabled: false and spec.config.platform.
platform-hostedplatform-gcp
Deployed byThe operatorYou
Issuer and secret keysA keyset file in a SecretA Cloud KMS key
Uploaded assetsA PersistentVolumeClaimA Cloud Storage bucket
Pod identityNone neededWorkload Identity Federation for GKE
EmailSMTP relay or SendGrid, configured in the installSMTP relay or SendGrid, configured in the service’s own file

Both run the identity server’s database on Cloud SQL.

The Kubernetes prerequisites apply, with the database, object storage, and email items replaced by the following.

  • A GKE cluster, Standard or Autopilot. For platform-gcp, the cluster must have Workload Identity Federation for GKE enabled. Autopilot clusters always do; a Standard cluster needs --workload-pool=PROJECT_ID.svc.id.goog and node pools that run the GKE metadata server.
  • A Cloud SQL for PostgreSQL instance with a private IP address on the cluster’s VPC network. The workloads connect to it directly. The operator’s pod template cannot add a Cloud SQL Auth Proxy sidecar, so public IP with the proxy is not an option for operator-managed workloads.
  • An ingress controller. This page uses ingress-nginx, which provisions a passthrough Network Load Balancer on GKE. Point the three DNS records at its external IP address.
  • Tools: gcloud, authenticated to a project where you can administer Cloud SQL, Cloud KMS, Cloud Storage, and IAM, in addition to kubectl, kustomize, git, jq, and Docker.

The platform-gcp image is published on Authwise’s internal registry. Get the image reference and a read credential for it from Authwise; the steps below call it PLATFORM_GCP_IMAGE.

The placeholders are PROJECT_ID, the project; LOCATION, a region such as us-central1; INSTANCE, the Cloud SQL instance name; NAMESPACE, the Kubernetes namespace the install lives in; and BUCKET, a globally unique bucket name.

  1. Require encrypted connections to the instance, and save its server certificate authority (CA):

    Terminal window
    gcloud sql instances patch INSTANCE --ssl-mode=ENCRYPTED_ONLY
    gcloud sql instances describe INSTANCE \
    --format='value(serverCaCert.cert)' > cloudsql-ca.pem
    gcloud sql instances describe INSTANCE --format='yaml(ipAddresses)'

    DB_HOST is the address whose type is PRIVATE. Cloud SQL issues a per-instance CA whose server certificate names the instance connection name, not the IP address, so the platform service verifies it with verify-ca rather than verify-full.

  2. Create the database user the install runs as:

    Terminal window
    gcloud sql users create authwise --instance=INSTANCE --password=DB_PASSWORD

    A user created this way holds the cloudsqlsuperuser role, which carries CREATEDB and CREATEROLE, so one user serves as the owner of both the identity server’s and the platform service’s databases. Splitting them is a hardening step, not a prerequisite.

  3. platform-gcp only. Create the key that wraps the issuer keys and the bucket that holds the assets:

    Terminal window
    gcloud kms keyrings create authwise --location=LOCATION
    gcloud kms keys create primary --keyring=authwise --location=LOCATION \
    --purpose=encryption
    gcloud storage buckets create gs://BUCKET --location=LOCATION \
    --uniform-bucket-level-access --public-access-prevention

    The key’s resource name is projects/PROJECT_ID/locations/LOCATION/keyRings/authwise/cryptoKeys/primary. The install’s issuerKeys.keyName carries that whole string. Cloud KMS key rings and keys cannot be deleted, and the service writes the key name into every ciphertext it produces, so choose the names once.

  4. platform-gcp only. Grant the service’s Kubernetes ServiceAccount access to the key and the bucket. The ServiceAccount, authwise-platform in NAMESPACE, is created in a later step; IAM accepts the binding before it exists.

    Terminal window
    PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format='value(projectNumber)')
    MEMBER="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/authwise-platform"
    gcloud kms keys add-iam-policy-binding primary \
    --keyring=authwise --location=LOCATION \
    --member="$MEMBER" --role=roles/cloudkms.cryptoKeyEncrypterDecrypter
    gcloud storage buckets add-iam-policy-binding gs://BUCKET \
    --member="$MEMBER" --role=roles/storage.objectAdmin
    gcloud storage buckets add-iam-policy-binding gs://BUCKET \
    --member="$MEMBER" --role=roles/storage.legacyBucketReader

    objectAdmin covers the objects and nothing else. The upload path reads the bucket’s metadata before it writes, which needs storage.buckets.get; legacyBucketReader is the narrowest role that grants it.

Nothing differs on GKE. Follow Install the operator.

Follow Prepare the namespace with these changes:

  • One database Secret serves both repository blocks, because one Cloud SQL user owns both databases:

    Terminal window
    kubectl -n NAMESPACE create secret generic authwise-db \
    --from-literal=owner-password=DB_PASSWORD
  • Store the Cloud SQL CA for the platform service:

    Terminal window
    kubectl -n NAMESPACE create secret generic cloudsql-ca \
    --from-file=ca.pem=cloudsql-ca.pem
  • For platform-hosted, generate the keyset and create the PersistentVolumeClaim as the Kubernetes page describes. GKE’s default StorageClass provisions a persistent disk for it, which is ReadWriteOnce; keep the platform service at one replica or move to platform-gcp.

  • For platform-gcp, skip the keyset and the claim.

Enrol the administrator’s password as on the Kubernetes page.

Skip this section if the operator deploys platform-hosted.

The identity server reaches the platform service over mutual TLS: the service presents a certificate for its Service name and requires a client certificate from every caller. The operator issues that material itself for the components it deploys. For a service you deploy, you issue it, with cert-manager, from a CA you create for the purpose.

  1. Create the ServiceAccount the IAM binding names:

    Terminal window
    kubectl -n NAMESPACE create serviceaccount authwise-platform
  2. Create the CA, the server certificate, and the client certificate the identity server presents:

    Terminal window
    kubectl -n NAMESPACE apply -f - <<'EOF'
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
    name: authwise-platform-selfsigned
    spec:
    selfSigned: {}
    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
    name: authwise-platform-ca
    spec:
    isCA: true
    commonName: authwise-platform-ca
    secretName: authwise-platform-ca
    privateKey:
    algorithm: ECDSA
    size: 256
    issuerRef:
    name: authwise-platform-selfsigned
    kind: Issuer
    ---
    apiVersion: cert-manager.io/v1
    kind: Issuer
    metadata:
    name: authwise-platform-ca
    spec:
    ca:
    secretName: authwise-platform-ca
    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
    name: authwise-platform-server
    spec:
    secretName: authwise-platform-server-tls
    dnsNames:
    - authwise-platform
    - authwise-platform.NAMESPACE.svc
    - authwise-platform.NAMESPACE.svc.cluster.local
    usages: [digital signature, key encipherment, server auth]
    issuerRef:
    name: authwise-platform-ca
    kind: Issuer
    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
    name: authwise-platform-client
    spec:
    secretName: authwise-platform-client-tls
    commonName: authwise-identity-server
    usages: [digital signature, key encipherment, client auth]
    issuerRef:
    name: authwise-platform-ca
    kind: Issuer
    EOF

    cert-manager writes ca.crt, tls.crt, and tls.key into both -tls Secrets, which is the layout the service and the identity server read.

  3. Write the service’s configuration. It reads the file named by CONFIG_PATHS; the server block is required, and every path points at a mount from the next step:

    Terminal window
    kubectl -n NAMESPACE create configmap authwise-platform-config \
    --from-literal=config.yaml='
    platform:
    server:
    host: 0.0.0.0
    port: 9989
    tlsCertPath: /etc/platform/tls/tls.crt
    tlsKeyPath: /etc/platform/tls/tls.key
    tlsCaPath: /etc/platform/tls/ca.crt
    '
  4. Deploy the service and its Service:

    Terminal window
    kubectl -n NAMESPACE apply -f - <<'EOF'
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: authwise-platform
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: authwise-platform
    template:
    metadata:
    labels:
    app: authwise-platform
    spec:
    serviceAccountName: authwise-platform
    imagePullSecrets:
    - name: authwise-registry
    containers:
    - name: platform
    image: PLATFORM_GCP_IMAGE
    env:
    - name: CONFIG_PATHS
    value: /etc/platform/config/config.yaml
    - name: PROJECT_ID
    value: PROJECT_ID
    - name: DATAFLOW_PREFIX
    value: authwise
    ports:
    - name: grpc
    containerPort: 9989
    readinessProbe:
    tcpSocket:
    port: grpc
    periodSeconds: 10
    livenessProbe:
    tcpSocket:
    port: grpc
    periodSeconds: 20
    volumeMounts:
    - name: config
    mountPath: /etc/platform/config
    readOnly: true
    - name: tls
    mountPath: /etc/platform/tls
    readOnly: true
    volumes:
    - name: config
    configMap:
    name: authwise-platform-config
    - name: tls
    secret:
    secretName: authwise-platform-server-tls
    ---
    apiVersion: v1
    kind: Service
    metadata:
    name: authwise-platform
    spec:
    selector:
    app: authwise-platform
    ports:
    - name: grpc
    port: 9989
    targetPort: grpc
    EOF

    PROJECT_ID scopes the service’s Pub/Sub client. DATAFLOW_PREFIX is required at startup, but an Authwise install never publishes to Pub/Sub, so no topic has to exist and any name works. The service’s gRPC health check answers only on the mutual TLS listener, which a kubelet probe cannot present a certificate to, so readiness is a TCP probe. The service validates its Cloud KMS and Cloud Storage clients at startup, and a pod that becomes ready did reach them.

    Credentials come from Workload Identity: the pod authenticates as its ServiceAccount, and the IAM bindings decide what it can reach. There is no key file to mount.

To send email through platform-gcp, add the same platform.messaging block and top-level repository block to its configuration that the operator renders for platform-hosted; messaging and the datastore are shared by every platform implementation and read the same keys. Passwords arrive as environment variables named after the key, such as PLATFORM_MESSAGING_SMTP_PASSWORD and REPOSITORY_GORM_OWNER_PASSWORD, never in the file.

The resource below is a complete install on Cloud SQL with the operator deploying platform-hosted. Save it as authwise.yaml and replace every placeholder. It differs from the Kubernetes sample in the database connections and in the platform service’s database TLS.

apiVersion: identity.authwise.com/v1alpha1
kind: AuthwiseIdentityServer
metadata:
name: authwise
namespace: NAMESPACE
spec:
preset: authwise-full
imagePullSecrets:
- name: authwise-registry
bootstrap:
admin:
issuerHost: ADMIN_HOST
username: ADMIN_EMAIL
audienceURI: https://API_HOST
credential: "PHC_STRING"
machineCredential: {}
platform:
hosted:
key:
keysetSecret:
name: authwise-platform-keyset
key: keyset.yaml
bucketObject:
buckets: [assets]
volumeClaimName: authwise-platform-objects
repository:
mode: gorm
tls:
# Cloud SQL's per-instance CA names the instance connection name,
# so verify the chain, not the hostname.
sslMode: verify-ca
caSecret:
name: cloudsql-ca
key: ca.pem
gorm:
app: &platformdb
host: DB_HOST
port: 5432
name: authwise_platform
username: authwise
passwordSecret:
name: authwise-db
key: owner-password
owner: *platformdb
messaging:
store: sql
email:
provider: smtp
from: no-reply@EXAMPLE_DOMAIN
smtp:
host: SMTP_HOST
port: 587
startTLS: true
console:
clientId: c-01
networking:
ingress:
className: nginx
issuer:
hosts: [ADMIN_HOST]
tlsSecretName: ADMIN_HOST_TLS_SECRET
api:
hosts: [API_HOST]
tlsSecretName: API_HOST_TLS_SECRET
console:
hosts: [CONSOLE_HOST]
tlsSecretName: CONSOLE_HOST_TLS_SECRET
config:
baseURL: https://ADMIN_HOST
serverSecret:
name: authwise-server
key: secret
repository:
mode: gorm
gorm:
app: &db
host: DB_HOST
port: 5432
name: authwise_identity
username: authwise
passwordSecret:
name: authwise-db
key: owner-password
owner: *db
issuerKeys:
keyName: primary
activeDays: 120
visibleAfterActiveDays: 60
asset:
mode: ObjectStorage
bucketName: assets

The placeholders not listed here are the Kubernetes sample’s.

PlaceholderValue
DB_HOSTThe Cloud SQL instance’s private IP address.
DB_PASSWORDThe password you gave gcloud sql users create, stored in the authwise-db Secret.

Google Cloud has no first-party transactional email service. The sample sends through an SMTP relay; provider: sendgrid with an apiKeySecret under sendgrid is the other operator-supported transport.

The identity server’s own database connection has no TLS fields in the resource. Its driver encrypts the connection when the server offers TLS, which ENCRYPTED_ONLY guarantees, but it does not verify the server certificate.

To point the install at the platform-gcp Deployment from Run platform-gcp, replace the whole platform block and the three config blocks shown here. Everything else stays as above.

spec:
platform:
enabled: false
config:
platform:
host: authwise-platform.NAMESPACE.svc
port: 9989
tls:
secretName: authwise-platform-client-tls
issuerKeys:
keyName: projects/PROJECT_ID/locations/LOCATION/keyRings/authwise/cryptoKeys/primary
activeDays: 120
visibleAfterActiveDays: 60
asset:
mode: ObjectStorage
bucketName: BUCKET

config.platform.host must be one of the server certificate’s dnsNames, because the identity server verifies the name it dials. keyName is the Cloud KMS key’s full resource name; the service sends it to Cloud KMS as the key to encrypt with, and the schema migration fails with invalid key if it names anything else. bucketName is the Cloud Storage bucket.

Follow Apply and watch and After the install. With platform-gcp, the REPLICAS count in the install’s status is one lower, because the platform workload is not the operator’s.

The Kubernetes troubleshooting table applies. These are the Google Cloud additions.

SymptomCause and fix
platform-gcp pod exits with PROJECT_ID is required or DATAFLOW_PREFIX is requiredBoth environment variables are required at startup. Set them on the container.
migrate fails with invalid keyissuerKeys.keyName is not the key’s full resource name, or the key is in a different location than the name says.
platform-gcp logs PermissionDenied from Cloud KMS, or does not have storage.buckets.get accessThe IAM binding is missing or names a different namespace or ServiceAccount than the pod runs as, or Workload Identity Federation is not enabled on the cluster or node pool.
Uploads fail with not foundasset.bucketName names a bucket that does not exist, or the ServiceAccount cannot read it.
Engine or api pods log x509: certificate signed by unknown authority dialing the platformThe client Secret in config.platform.tls.secretName was not issued by the CA that signed the server certificate.
Engine or api pods log certificate is valid for ... not ... dialing the platformconfig.platform.host is not in the server certificate’s dnsNames.
platform-hosted crash-loops with a database TLS errorverify-full cannot match Cloud SQL’s certificate to an IP address. Use verify-ca.
platform-hosted stays Pending with a volume errorThe persistent disk is ReadWriteOnce; the platform runs more than one replica, or a previous pod still holds the disk.