Add authenticated endpoint.

This commit is contained in:
Tom Alexander
2024-10-15 23:03:52 -04:00
parent f9d3c551f0
commit 9ea4952327
14 changed files with 501 additions and 29 deletions

View File

@@ -60,3 +60,22 @@ provider "registry.terraform.io/hashicorp/random" {
"zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af",
]
}
provider "registry.terraform.io/hashicorp/tls" {
version = "4.0.6"
hashes = [
"h1:dYSb3V94K5dDMtrBRLPzBpkMTPn+3cXZ/kIJdtFL+2M=",
"zh:10de0d8af02f2e578101688fd334da3849f56ea91b0d9bd5b1f7a243417fdda8",
"zh:37fc01f8b2bc9d5b055dc3e78bfd1beb7c42cfb776a4c81106e19c8911366297",
"zh:4578ca03d1dd0b7f572d96bd03f744be24c726bfd282173d54b100fd221608bb",
"zh:6c475491d1250050765a91a493ef330adc24689e8837a0f07da5a0e1269e11c1",
"zh:81bde94d53cdababa5b376bbc6947668be4c45ab655de7aa2e8e4736dfd52509",
"zh:abdce260840b7b050c4e401d4f75c7a199fafe58a8b213947a258f75ac18b3e8",
"zh:b754cebfc5184873840f16a642a7c9ef78c34dc246a8ae29e056c79939963c7a",
"zh:c928b66086078f9917aef0eec15982f2e337914c5c4dbc31dd4741403db7eb18",
"zh:cded27bee5f24de6f2ee0cfd1df46a7f88e84aaffc2ecbf3ff7094160f193d50",
"zh:d65eb3867e8f69aaf1b8bb53bd637c99c6b649ba3db16ded50fa9a01076d1a27",
"zh:ecb0c8b528c7a619fa71852bb3fb5c151d47576c5aab2bf3af4db52588722eeb",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
]
}

View File

@@ -11,8 +11,41 @@ resource "google_cloud_run_v2_service" "api_server" {
ports {
container_port = 8080
}
env {
name = "JWT_PUBLIC_KEY"
value = tls_private_key.jwt_private_key.public_key_pem
}
env {
name = "JWT_PRIVATE_KEY"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.jwt_private_key.secret_id
version = "latest"
}
}
}
env {
name = "JWT_CLIENT_ID"
value = random_uuid.jwt_client_id.result
}
env {
name = "JWT_GATEWAY_ADDRESS"
value = "gateway-to-the-api-etf4fzq.uc.gateway.dev"
# value = google_api_gateway_gateway.gateway.default_hostname
# TODO: This causes a cycle. Perhaps cloud run has a default env variable with this information.
}
}
}
depends_on = [google_project_service.service["run"], ]
}
resource "google_cloud_run_service_iam_binding" "public" {
project = google_cloud_run_v2_service.api_server.project
location = google_cloud_run_v2_service.api_server.location
service = google_cloud_run_v2_service.api_server.name
role = "roles/run.invoker"
members = [
"allUsers"
]
}

View File

@@ -1,3 +1,6 @@
resource "random_uuid" "jwt_client_id" {
}
resource "google_api_gateway_api" "api" {
provider = google-beta
project = google_project.project.project_id
@@ -13,31 +16,8 @@ resource "google_api_gateway_api_config" "api_config" {
openapi_documents {
document {
path = "spec.yaml"
contents = base64encode(<<-EOF
swagger: "2.0"
info:
title: the-gateway foo
description: "Run auth through Google API Gateway."
version: "1.0.0"
schemes:
- "https"
produces:
- application/json
x-google-backend:
address: ${google_cloud_run_v2_service.api_server.uri}
paths:
"/":
get:
description: "Hello World."
operationId: "helloWorld"
responses:
200:
description: "Success."
schema:
type: string
EOF
)
path = "spec.yaml"
contents = base64encode(templatefile("openapi_spec.yaml", { backend_url = google_cloud_run_v2_service.api_server.uri, client_id = random_uuid.jwt_client_id.result }))
}
}
}
@@ -47,9 +27,20 @@ resource "google_api_gateway_gateway" "gateway" {
project = google_project.project.project_id
api_config = google_api_gateway_api_config.api_config.id
gateway_id = "gateway-to-the-api"
# Delete this when api_config changes, otherwise if api_config needs to be replaced, it errors out because it is "in use" by this gateway. I wish this could be triggered only when api_config is being replaced instead of all edits.
lifecycle {
replace_triggered_by = [
google_api_gateway_api_config.api_config
]
}
}
output "gateway_address" {
value = google_api_gateway_gateway.gateway.default_hostname
}
output "client_id" {
value = random_uuid.jwt_client_id.result
}

39
terraform/jwt_key.tf Normal file
View File

@@ -0,0 +1,39 @@
#
# Warning: This file contains terraform that will lead to secrets being stored in the terraform state file unencrypted. It is not suitable for a production deploy, but since this is a test/experiment, the additional automation outweighed the potential risk.
#
resource "tls_private_key" "jwt_private_key" {
algorithm = "RSA"
rsa_bits = 2048
}
resource "google_secret_manager_secret" "jwt_private_key" {
project = google_project.project.project_id
secret_id = "jwt-private-key"
replication {
auto {}
}
depends_on = [google_project_service.service["secretmanager"], ]
}
resource "google_secret_manager_secret_version" "jwt_private_key" {
secret = google_secret_manager_secret.jwt_private_key.id
secret_data = tls_private_key.jwt_private_key.private_key_pem
}
resource "google_secret_manager_secret_iam_member" "member" {
project = google_secret_manager_secret.jwt_private_key.project
secret_id = google_secret_manager_secret.jwt_private_key.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_project.project.number}-compute@developer.gserviceaccount.com"
# TODO: This should probably be using a service account specific to the cloud run service instead of the compute service agent.
}
output "jwt_private_key" {
value = tls_private_key.jwt_private_key.private_key_pem
sensitive = true
}

View File

@@ -74,7 +74,7 @@ resource "google_project" "project" {
resource "google_project_service" "service" {
project = google_project.project.project_id
for_each = toset(["run", "artifactregistry", "apigateway"])
for_each = toset(["run", "artifactregistry", "apigateway", "secretmanager"])
service = "${each.key}.googleapis.com"
disable_dependent_services = true
}

View File

@@ -0,0 +1,61 @@
swagger: "2.0"
info:
title: the-gateway foo
description: "Run auth through Google API Gateway."
version: "1.0.0"
schemes:
- "https"
produces:
- application/json
x-google-backend:
address: ${backend_url}
paths:
"/":
get:
description: "Hello World."
operationId: "helloWorld"
responses:
200:
description: "Success."
schema:
type: string
"/.well-known/jwks.json":
get:
description: "JWKS."
operationId: "jwks"
responses:
200:
description: "Success."
schema:
type: string
"/some_protected_endpoint":
get:
description: "An endpoint that requires auth."
operationId: "someProtectedEndpoint"
security:
- your_custom_auth_id: []
responses:
200:
description: "Success."
schema:
type: string
"/get_short_lived_token":
get:
description: "An endpoint that gives a short-lived JWT."
operationId: "getShortLivedToken"
security:
- your_custom_auth_id: []
responses:
200:
description: "Success."
schema:
type: string
securityDefinitions:
your_custom_auth_id:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
# The value below should be unique
x-google-issuer: "issuer of the token"
x-google-jwks_uri: "${backend_url}/.well-known/jwks.json"
x-google-audiences: "${client_id}"