mrparkers / terraform-provider-keycloak

Terraform provider for Keycloak
https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs
MIT License
592 stars 291 forks source link

Feature jwt auth #938

Open spunkedy opened 4 months ago

spunkedy commented 4 months ago

With a client id / secret, to be secure you will have to go through a rotation. If you do a JWT signer, it's a little bit more complex, but you can still get the client going.

here is an example with grabbing the jwt from a signed aws kms so that you aren't responsible for getting the details.

#!/bin/bash

# Variables
KMS_KEY_ARN="arn_here"
ALGORITHM="RSASSA_PKCS1_V1_5_SHA_256"
CLIENT_ID="test"
AUTH_SERVER_TOKEN_ENDPOINT_URL="http://localhost:8080/realms/master/protocol/openid-connect/token"

# Helper functions
base64url_encode() {
    openssl enc -base64 -A | tr '+/' '-_' | tr -d '='
}

generate_jti() {
    # Generates a random string for jti using /dev/urandom
    head -c 16 /dev/urandom | base64 | tr -d '/+=' | cut -c -22
}

# JWT Claims
ISSUER="$CLIENT_ID" # Issuer
SUBJECT="$CLIENT_ID" # Subject
AUDIENCE="$AUTH_SERVER_TOKEN_ENDPOINT_URL" # Audience
JTI=$(generate_jti) # JWT ID
CURRENT_TIME=$(date +%s)
EXPIRATION_TIME=$(($CURRENT_TIME + 3600)) # 1 hour from now

# Create JWT Header
HEADER=$(jq -n --arg alg "RS256" --arg typ "JWT" '{alg: $alg, typ: $typ}')
HEADER_BASE64=$(echo -n "$HEADER" | base64url_encode)

# Create JWT Payload
PAYLOAD=$(jq -n \
    --arg iss "$ISSUER" \
    --arg sub "$SUBJECT" \
    --arg aud "$AUDIENCE" \
    --arg jti "$JTI" \
    --argjson iat $CURRENT_TIME \
    --argjson exp $EXPIRATION_TIME \
    '{iss: $iss, sub: $sub, aud: $aud, jti: $jti, iat: $iat, exp: $exp}')
PAYLOAD_BASE64=$(echo -n "$PAYLOAD" | base64url_encode)

# Prepare the message to be signed
MESSAGE="$HEADER_BASE64.$PAYLOAD_BASE64"

# Use AWS KMS to sign the message
SIGNATURE_JSON=$(echo -n "$MESSAGE" | aws kms sign \
    --key-id "$KMS_KEY_ARN" \
    --message-type RAW \
    --signing-algorithm "$ALGORITHM" \
    --message fileb:///dev/stdin \
    --output json)
SIGNATURE=$(echo $SIGNATURE_JSON | jq -r '.Signature' | base64 -d | base64url_encode)

# Construct the JWT
JWT="$HEADER_BASE64.$PAYLOAD_BASE64.$SIGNATURE"

echo "jwt = \"$JWT\"" > terraform.tfvars

you can then use it like:

variable "jwt" {
  type        = string
  description = "JWT for client authorization"
}

provider "keycloak" {
  client_assertion = var.jwt
  url = "http://localhost:8080"
}
spunkedy commented 4 months ago

Just updated the logic since the JTI of the token isn't allowed to be reused and the existing logic retried.

Looking for feedback and I can update this, if it's better to do it a different way let me know.