# ---------------------------------------------------------------------------------------------------------------------
# TERRAGRUNT CONFIGURATION
# ---------------------------------------------------------------------------------------------------------------------

terraform {
  source = "git@gitlab.infrastructure.immuta.io:infrastructure/terraform/internal-modules/aws/security/kubernetes-iam-serviceaccount.git?ref=v5.0.0"
}

# Include all settings from the root terragrunt.hcl file
include {
  path = find_in_parent_folders()
}

# ---------------------------------------------------------------------------------------------------------------------
# Locals are named constants that are reusable within the configuration.
# ---------------------------------------------------------------------------------------------------------------------
locals {
  # Automatically load common variables shared across all accounts
  common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl"))

  # Automatically load account-level variables
  account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))

  # Automatically load region-level variables
  region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  # Extract the region and environment for easy access
  aws_region           = local.region_vars.locals.aws_region
  aws_region_shortname = local.region_vars.locals.aws_region_shortname
  default_region       = local.common_vars.locals.default_region
  env                  = local.account_vars.locals.env

  # A local for more convenient access to the accounts map.
  accounts     = local.common_vars.locals.accounts
  account_name = local.account_vars.locals.account_name
  account_domain_name = local.account_vars.locals.domain_name

  cidr_blocks                = local.common_vars.locals.cidr_blocks
  cloud_account_domain_names = local.common_vars.locals.cloud_account_domain_names
  cloud_portal_domain_names  = local.common_vars.locals.cloud_portal_domain_names

  current_dir = run_cmd("--terragrunt-quiet", "basename", get_terragrunt_dir())
  tags = merge(local.common_vars.locals.global_tags, {
    tfpath = "/${local.account_vars.locals.domain_name}/${local.aws_region}/${local.current_dir}"
  })
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE DEPENDENCIES
# ---------------------------------------------------------------------------------------------------------------------

dependency cluster {
  config_path = "../eks-platform-mgmt-use1-1"
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE PARAMETERS
# These are the variables we have to pass in to use the module specified in the terragrunt configuration above
# ---------------------------------------------------------------------------------------------------------------------
inputs = {
  role_name = "teleport-db-bot"

  assume_role_entities = [
    {
      namespace = "tbot-database-proxy"
      service_account_name = "teleport-db-bot"
      identity_provider_issuer = dependency.cluster.outputs.cluster_oidc_issuer_url
      identity_provider_arn = dependency.cluster.outputs.cluster_oidc_provider_arn
    }
  ]
}


Good — module version is `7.0.0`, pattern is clear. Now I'll write the terragrunt config.


Bash

Create ruth-service-account terragrunt directory


(Bash completed with no output)


Write

# ---------------------------------------------------------------------------------------------------------------------
# TERRAGRUNT CONFIGURATION
# ---------------------------------------------------------------------------------------------------------------------

terraform {
  source = "tfr://crow.infrastructure.immuta.io/internal/security-kubernetes-iam-serviceaccount/aws?version=7.0.0"
}

# Include all settings from the root terragrunt.hcl file
include {
  path = find_in_parent_folders()
}

# ---------------------------------------------------------------------------------------------------------------------
# Locals are named constants that are reusable within the configuration.
# ---------------------------------------------------------------------------------------------------------------------
locals {
  # Automatically load common variables shared across all accounts
  common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl"))

  # Automatically load account-level variables
  account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))

  # Automatically load region-level variables
  region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  # Extract the region and environment for easy access
  aws_region           = local.region_vars.locals.aws_region
  aws_region_shortname = local.region_vars.locals.aws_region_shortname
  env                  = local.account_vars.locals.env

  accounts     = local.common_vars.locals.accounts
  account_name = local.account_vars.locals.account_name

  current_dir = run_cmd("--terragrunt-quiet", "basename", get_terragrunt_dir())
  tags = merge(local.common_vars.locals.global_tags, {
    tfpath = "/${local.account_vars.locals.domain_name}/${local.aws_region}/${local.current_dir}"
  })
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE DEPENDENCIES
# ---------------------------------------------------------------------------------------------------------------------

dependency cluster {
  config_path = "../eks-platform-mgmt-use1-1"
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE PARAMETERS
# These are the variables we have to pass in to use the module specified in the terragrunt configuration above
# ---------------------------------------------------------------------------------------------------------------------
inputs = {
  role_name = "ruth-plugin"

  assume_role_entities = [
    {
      namespace            = "ruth"
      service_account_name = "ruth"
      identity_provider_issuer = dependency.cluster.outputs.cluster_oidc_issuer_url
      identity_provider_arn    = dependency.cluster.outputs.cluster_oidc_provider_arn
    }
  ]

  policy_statements = [
    {
      sid = "BedrockInvokeNemotron"
      actions = [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream",
      ]
      # Verify the model ID in the Bedrock console — it must match what is set
      # in ruth.toml [ai.bedrock] model_id. The cross-region inference prefix
      # (us.) may be needed if RUTH is configured to use an inference profile
      # rather than calling the foundation model directly.
      resources = [
        "arn:aws:bedrock:${local.aws_region}::foundation-model/nvidia.nemotron-nano-2-4b-instruct-v1:0",
      ]
    }
  ]

  tags = local.tags
}


File created successfully at: /Users/steve.briggs/projects/customer-platform-infrastructure/platform-management/us-east-1/ruth-service-account/terragrunt.hcl (file state is current in your context — no need to Read it back)

# ---------------------------------------------------------------------------------------------------------------------
# TERRAGRUNT CONFIGURATION
# ---------------------------------------------------------------------------------------------------------------------

terraform {
  source = "tfr://crow.infrastructure.immuta.io/internal/security-kubernetes-iam-serviceaccount/aws?version=7.0.0"
}

# Include all settings from the root terragrunt.hcl file
include {
  path = find_in_parent_folders()
}

# ---------------------------------------------------------------------------------------------------------------------
# Locals are named constants that are reusable within the configuration.
# ---------------------------------------------------------------------------------------------------------------------
locals {
  # Automatically load common variables shared across all accounts
  common_vars = read_terragrunt_config(find_in_parent_folders("common.hcl"))

  # Automatically load account-level variables
  account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))

  # Automatically load region-level variables
  region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  # Extract the region and environment for easy access
  aws_region           = local.region_vars.locals.aws_region
  aws_region_shortname = local.region_vars.locals.aws_region_shortname
  env                  = local.account_vars.locals.env

  accounts     = local.common_vars.locals.accounts
  account_name = local.account_vars.locals.account_name

  current_dir = run_cmd("--terragrunt-quiet", "basename", get_terragrunt_dir())
  tags = merge(local.common_vars.locals.global_tags, {
    tfpath = "/${local.account_vars.locals.domain_name}/${local.aws_region}/${local.current_dir}"
  })
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE DEPENDENCIES
# ---------------------------------------------------------------------------------------------------------------------

dependency cluster {
  config_path = "../eks-platform-mgmt-use1-1"
}

# ---------------------------------------------------------------------------------------------------------------------
# MODULE PARAMETERS
# These are the variables we have to pass in to use the module specified in the terragrunt configuration above
# ---------------------------------------------------------------------------------------------------------------------
inputs = {
  role_name = "ruth-plugin"

  assume_role_entities = [
    {
      namespace            = "ruth"
      service_account_name = "ruth"
      identity_provider_issuer = dependency.cluster.outputs.cluster_oidc_issuer_url
      identity_provider_arn    = dependency.cluster.outputs.cluster_oidc_provider_arn
    }
  ]

  policy_statements = [
    {
      sid = "BedrockInvokeNemotron"
      actions = [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream",
      ]
      # Verify the model ID in the Bedrock console — it must match what is set
      # in ruth.toml [ai.bedrock] model_id. The cross-region inference prefix
      # (us.) may be needed if RUTH is configured to use an inference profile
      # rather than calling the foundation model directly.
      resources = [
        "arn:aws:bedrock:${local.aws_region}::foundation-model/nvidia.nemotron-nano-2-4b-instruct-v1:0",
      ]
    }
  ]

  tags = local.tags
}


Ruth will live in the eks-platform-mgmt-use1-1 cluster. I need to setup a terraform (terragrunt) module to create a service account for RUTH that gives it the appropriate bedrock access (I want to use…


Set up RUTH plugin Teleport authentication in Kubernetes


Written to `platform-management/us-east-1/ruth-service-account/terragrunt.hcl`.

A few things to confirm before running this:

**Model ID** — I used `nvidia.nemotron-nano-2-4b-instruct-v1:0` but you should verify it in the Bedrock console or with `aws bedrock list-foundation-models --region us-east-1 | grep -i nemotron`. If RUTH is configured to call a cross-region inference profile (the `us.` prefix in the model ID, like the claude model in `ruth.toml.example`), you'll also need to add the inference profile ARN as a second resource:
```hcl
"arn:aws:bedrock:${local.aws_region}:*:inference-profile/us.nvidia.nemotron-nano-2-4b-instruct-v1:0",
```

**ServiceAccount annotation** — once this runs, the IAM role ARN from `outputs.iam_role_arn` needs to be annotated onto the Kubernetes ServiceAccount. Add this to the `deploy/deployment.yaml` ServiceAccount:
```yaml
annotations:
  eks.amazonaws.com/role-arn: <iam_role_arn>
```

**`ruth.toml` model_id** — you'll want to update `deploy/deployment.yaml`'s ConfigMap to use the Nemotron model ID instead of the Claude one in the example.


afplay ~/.claude/sounds/mario/stop/stomp.wav 2>/dev/null || true


Setting up RUTH's Kubernetes deployment with tbot sidecar for Teleport auth and IRSA for Bedrock access. Next: verify the Nemotron model ID and add the IAM role ARN annotation to the ServiceAccount in deploy/deployment.yaml. (disable recaps in /config)


<command-message>obsidian-save</command-message>
<command-name>/obsidian-save</command-name>


Save current conversation context to an Obsidian note under the `Claude/` directory.

## What to save

Capture all of the following that are relevant to the current conversation:

- **Conversation summary**: What the user is trying to accomplish and why
- **Project context**: Relevant architecture, design decisions, or constraints discovered
- **Task state**: What has been completed, what is in progress, what is blocked or pending
- **Key decisions**: Any choices made and the reasoning behind them
- **Open questions**: Unresolved questions or things to revisit
- **Next steps**: Concrete actions that should follow from this session

## File location

Derive the project name from the current working directory (use the repo or app folder name). Save to:

```
Claude/<project-name>/<YYYY-MM-DD>-<short-slug>.md
```

Use today's date and a short 2-4 word slug describing the session topic (e.g. `2026-04-29-ruth-plugin-setup.md`).

If `` is provided, use it as the slug instead of generating one.

## Note format

Use this structure:

```markdown
---
date: <YYYY-MM-DD>
project: <project-name>
tags: [claude-context]
---

# <Session Title>

## Summary
<1-3 sentence overview of what this session was about>

## Context
<Relevant project background, architecture notes, constraints>

## Decisions
<Decisions made and rationale>

## Task State
<What was completed, in progress, or blocked>

## Open Questions
<Anything unresolved>

## Next Steps
<Concrete follow-up actions>
```

## Steps

1. Determine the project name from the working directory.
2. Determine today's date.
3. Draft the note content based on the current conversation.
4. Use `mcp__obsidian__create_vault_file` to write the note to the vault.
5. Confirm to the user where the note was saved.


---
date: 2026-05-07
project: ruth
tags: [claude-context]
---

# RUTH Kubernetes Deployment Setup

## Summary
Session focused on preparing RUTH (Teleport access-request plugin) for deployment to the `eks-platform-mgmt-use1-1` cluster. Covered Teleport authentication options, produced a base Kubernetes deployment manifest with a tbot sidecar, and created a Terragrunt module for the IRSA service account with Bedrock permissions.

## Context
- RUTH is a Go-based Teleport access-request plugin that scores requests via deterministic rules + AI (Bedrock), auto-approves low-risk ones, and files Jira ITSM tickets for the rest.
- Deployed to `eks-platform-mgmt-use1-1` (us-east-1, platform-management account), likely in its own `ruth` namespace.
- Teleport cluster is also on Kubernetes. RUTH needs a Teleport identity file to authenticate as a bot.
- RUTH uses `client.LoadIdentityFile(cfg.Teleport.IdentityFile)` (`app.go:38`) — needs a file path to a valid identity.
- AI provider: switching to NVIDIA Nemotron Nano 2 (Bedrock) instead of the Claude model in the example config.
- Bedrock calls use `bedrockruntime:Converse` API → IAM action `bedrock:InvokeModel`.

## Decisions

### Teleport auth: tbot sidecar (Machine ID)
- Chose tbot sidecar over static identity file Secret or separate tbot Deployment.
- tbot joins via Kubernetes join method (verifies pod service account JWT against Teleport's OIDC config — no static bootstrap secret needed).
- tbot writes `/var/lib/teleport-bot/identity` to a shared `emptyDir`; RUTH reads the same path.
- An initContainer (`wait-for-identity`) busy-waits on the file before RUTH starts, preventing `LoadIdentityFile` from failing on a missing file at startup.

### Reputation persistence
- Used `emptyDir` for `/var/lib/ruth` (reputation.json) for now. Noted this doesn't survive pod replacement — swap for a PVC if durability is needed.

### Terragrunt source
- Used TFR registry format as specified: `tfr://crow.infrastructure.immuta.io/internal/security-kubernetes-iam-serviceaccount/aws?version=7.0.0`
- Module version `7.0.0` confirmed from VERSION file in the local module copy.

## Task State

### Completed
- `apps/ruth/deploy/deployment.yaml` — base Kubernetes manifest with:
  - `Namespace`, `ServiceAccount`, `Secret` (Jira token), `ConfigMap` (ruth.toml), `ConfigMap` (tbot.yaml), `Deployment`
  - tbot sidecar + shared `emptyDir` volume
  - initContainer waiting for identity file
  - RUTH config pointing `identity_file` at `/var/lib/teleport-bot/identity`
- `platform-management/us-east-1/ruth-service-account/terragrunt.hcl` — IRSA module with:
  - `assume_role_entities` bound to namespace `ruth`, service account `ruth`, OIDC from cluster dependency
  - `policy_statements` granting `bedrock:InvokeModel` + `bedrock:InvokeModelWithResponseStream` on the Nemotron model ARN

### Pending / Not started
- Kubernetes overlay (user will create this on top of the base manifest)
- Teleport-side prerequisites (Bot, ProvisionToken) — commands are in the deployment.yaml comment header
- Terragrunt apply for `ruth-service-account`
- Add `eks.amazonaws.com/role-arn` annotation to the ServiceAccount after terraform apply
- Update `model_id` in the ruth.toml ConfigMap to the Nemotron model ID

## Open Questions

1. **Nemotron Nano 2 model ID** — placeholder used: `nvidia.nemotron-nano-2-4b-instruct-v1:0`. Must be verified with:
   ```sh
   aws bedrock list-foundation-models --region us-east-1 | grep -i nemotron
   ```
2. **Cross-region inference profile** — if RUTH is configured with a `us.` prefixed model ID (inference profile rather than direct foundation model), the Bedrock policy resource needs a second ARN:
   ```hcl
   "arn:aws:bedrock:${local.aws_region}:*:inference-profile/us.nvidia.nemotron-nano-2-4b-instruct-v1:0"
   ```
3. **tbot ProvisionToken name** — placeholder `ruth-bot` used in `tbot.yaml`; must match the token name produced by `tctl tokens add`.
4. **Reputation durability** — decide whether `emptyDir` is acceptable or a PVC is needed for `reputation.json`.

## Next Steps
1. Verify Nemotron Nano 2 model ID in Bedrock console; update `tbot.yaml` ConfigMap and terragrunt policy resource accordingly.
2. Run `tctl` commands from the `deploy/deployment.yaml` comment header to create the Teleport Bot and ProvisionToken.
3. Run `terragrunt apply` in `ruth-service-account/`; capture the output `iam_role_arn`.
4. Add `eks.amazonaws.com/role-arn: <iam_role_arn>` annotation to the `ruth` ServiceAccount in `deploy/deployment.yaml`.
5. Build and push the RUTH container image; update the `image:` placeholder in the Deployment.
6. Create a kustomize overlay on top of `deploy/deployment.yaml` for environment-specific values (proxy addr, Jira URL, OTEL endpoint, etc.).
7. Deploy and verify tbot joins successfully (`kubectl logs -c tbot`), then confirm RUTH connects to Teleport.


File created successfully

File created successfully


Saved to `Claude/ruth/2026-05-07-ruth-k8s-deployment.md` in your Obsidian vault.


Setting up RUTH for Kubernetes deployment with a tbot sidecar for Teleport auth and IRSA for Bedrock access. Both manifests are written — next step is verifying the NVIDIA Nemotron Nano 2 model ID in the Bedrock console. (disable recaps in /config)