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
gcpblock: it does not deploy platform-gcp. You run it as a Deployment in the install’s namespace and point the install at it withspec.platform.enabled: falseandspec.config.platform.
| platform-hosted | platform-gcp | |
|---|---|---|
| Deployed by | The operator | You |
| Issuer and secret keys | A keyset file in a Secret | A Cloud KMS key |
| Uploaded assets | A PersistentVolumeClaim | A Cloud Storage bucket |
| Pod identity | None needed | Workload Identity Federation for GKE |
| SMTP relay or SendGrid, configured in the install | SMTP relay or SendGrid, configured in the service’s own file |
Both run the identity server’s database on Cloud SQL.
Before you begin
Section titled “Before you begin”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.googand 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 tokubectl,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.
Create the Google Cloud resources
Section titled “Create the Google Cloud resources”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.
-
Require encrypted connections to the instance, and save its server certificate authority (CA):
Terminal window gcloud sql instances patch INSTANCE --ssl-mode=ENCRYPTED_ONLYgcloud sql instances describe INSTANCE \--format='value(serverCaCert.cert)' > cloudsql-ca.pemgcloud sql instances describe INSTANCE --format='yaml(ipAddresses)'DB_HOSTis the address whosetypeisPRIVATE. 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 withverify-carather thanverify-full. -
Create the database user the install runs as:
Terminal window gcloud sql users create authwise --instance=INSTANCE --password=DB_PASSWORDA user created this way holds the
cloudsqlsuperuserrole, which carriesCREATEDBandCREATEROLE, 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. -
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=LOCATIONgcloud kms keys create primary --keyring=authwise --location=LOCATION \--purpose=encryptiongcloud storage buckets create gs://BUCKET --location=LOCATION \--uniform-bucket-level-access --public-access-preventionThe key’s resource name is
projects/PROJECT_ID/locations/LOCATION/keyRings/authwise/cryptoKeys/primary. The install’sissuerKeys.keyNamecarries 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. -
platform-gcp only. Grant the service’s Kubernetes ServiceAccount access to the key and the bucket. The ServiceAccount,
authwise-platforminNAMESPACE, 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.cryptoKeyEncrypterDecryptergcloud storage buckets add-iam-policy-binding gs://BUCKET \--member="$MEMBER" --role=roles/storage.objectAdmingcloud storage buckets add-iam-policy-binding gs://BUCKET \--member="$MEMBER" --role=roles/storage.legacyBucketReaderobjectAdmincovers the objects and nothing else. The upload path reads the bucket’s metadata before it writes, which needsstorage.buckets.get;legacyBucketReaderis the narrowest role that grants it.
Install the operator
Section titled “Install the operator”Nothing differs on GKE. Follow Install the operator.
Prepare the namespace
Section titled “Prepare the namespace”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
PersistentVolumeClaimas the Kubernetes page describes. GKE’s defaultStorageClassprovisions a persistent disk for it, which isReadWriteOnce; 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.
Run platform-gcp
Section titled “Run platform-gcp”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.
-
Create the ServiceAccount the IAM binding names:
Terminal window kubectl -n NAMESPACE create serviceaccount authwise-platform -
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/v1kind: Issuermetadata:name: authwise-platform-selfsignedspec:selfSigned: {}---apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: authwise-platform-caspec:isCA: truecommonName: authwise-platform-casecretName: authwise-platform-caprivateKey:algorithm: ECDSAsize: 256issuerRef:name: authwise-platform-selfsignedkind: Issuer---apiVersion: cert-manager.io/v1kind: Issuermetadata:name: authwise-platform-caspec:ca:secretName: authwise-platform-ca---apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: authwise-platform-serverspec:secretName: authwise-platform-server-tlsdnsNames:- authwise-platform- authwise-platform.NAMESPACE.svc- authwise-platform.NAMESPACE.svc.cluster.localusages: [digital signature, key encipherment, server auth]issuerRef:name: authwise-platform-cakind: Issuer---apiVersion: cert-manager.io/v1kind: Certificatemetadata:name: authwise-platform-clientspec:secretName: authwise-platform-client-tlscommonName: authwise-identity-serverusages: [digital signature, key encipherment, client auth]issuerRef:name: authwise-platform-cakind: IssuerEOFcert-manager writes
ca.crt,tls.crt, andtls.keyinto both-tlsSecrets, which is the layout the service and the identity server read. -
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.0port: 9989tlsCertPath: /etc/platform/tls/tls.crttlsKeyPath: /etc/platform/tls/tls.keytlsCaPath: /etc/platform/tls/ca.crt' -
Deploy the service and its Service:
Terminal window kubectl -n NAMESPACE apply -f - <<'EOF'apiVersion: apps/v1kind: Deploymentmetadata:name: authwise-platformspec:replicas: 1selector:matchLabels:app: authwise-platformtemplate:metadata:labels:app: authwise-platformspec:serviceAccountName: authwise-platformimagePullSecrets:- name: authwise-registrycontainers:- name: platformimage: PLATFORM_GCP_IMAGEenv:- name: CONFIG_PATHSvalue: /etc/platform/config/config.yaml- name: PROJECT_IDvalue: PROJECT_ID- name: DATAFLOW_PREFIXvalue: authwiseports:- name: grpccontainerPort: 9989readinessProbe:tcpSocket:port: grpcperiodSeconds: 10livenessProbe:tcpSocket:port: grpcperiodSeconds: 20volumeMounts:- name: configmountPath: /etc/platform/configreadOnly: true- name: tlsmountPath: /etc/platform/tlsreadOnly: truevolumes:- name: configconfigMap:name: authwise-platform-config- name: tlssecret:secretName: authwise-platform-server-tls---apiVersion: v1kind: Servicemetadata:name: authwise-platformspec:selector:app: authwise-platformports:- name: grpcport: 9989targetPort: grpcEOFPROJECT_IDscopes the service’s Pub/Sub client.DATAFLOW_PREFIXis 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.
Write the install
Section titled “Write the install”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/v1alpha1kind: AuthwiseIdentityServermetadata: name: authwise namespace: NAMESPACEspec: 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: assetsThe placeholders not listed here are the Kubernetes sample’s.
| Placeholder | Value |
|---|---|
DB_HOST | The Cloud SQL instance’s private IP address. |
DB_PASSWORD | The 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.
Use platform-gcp instead
Section titled “Use platform-gcp instead”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: BUCKETconfig.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.
Apply and watch
Section titled “Apply and watch”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.
Troubleshooting
Section titled “Troubleshooting”The Kubernetes troubleshooting table applies. These are the Google Cloud additions.
| Symptom | Cause and fix |
|---|---|
platform-gcp pod exits with PROJECT_ID is required or DATAFLOW_PREFIX is required | Both environment variables are required at startup. Set them on the container. |
migrate fails with invalid key | issuerKeys.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 access | The 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 found | asset.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 platform | The 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 platform | config.platform.host is not in the server certificate’s dnsNames. |
| platform-hosted crash-loops with a database TLS error | verify-full cannot match Cloud SQL’s certificate to an IP address. Use verify-ca. |
platform-hosted stays Pending with a volume error | The persistent disk is ReadWriteOnce; the platform runs more than one replica, or a previous pod still holds the disk. |