Configure operator identities for staging

779ce2e4de13 · AtlantisPleb · · parent 3deddf346c60

Configure operator identities for staging

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified config/runtime.exs
  • modified docs/runtime-configuration.md
  • added lib/openagents/accounts/operator_config.ex
  • added test/openagents/accounts/operator_config_test.exs

Diff

4 files changed, +56 -0

config/runtime.exs modified +6

@@ -114,6 +114,11 @@ if config_env() == :prod and runtime_role == :web do

114 114
  ecto_ipv6? = parse_boolean.("OPENAGENTS_DATABASE_IPV6")
115 115
  pool_size = parse_integer.("POOL_SIZE", 1..200)
116 116
117
  admin_github_ids =
118
    "OPENAGENTS_ADMIN_GITHUB_IDS"
119
    |> optional_text.()
120
    |> OpenAgents.Accounts.OperatorConfig.parse_github_ids!()
121
117 122
  repo_config =
118 123
    case required_text.("OPENAGENTS_DATABASE_MODE") do
119 124
      "url" ->

@@ -313,6 +318,7 @@ if config_env() == :prod and runtime_role == :web do

313 318
    staging_cleanup_enabled: staging_cleanup_enabled,
314 319
    production_deploy_enabled: production_deploy_enabled,
315 320
    build_revision: OpenAgents.BuildInfo.revision(),
321
    admin_github_ids: admin_github_ids,
316 322
    image_digest: optional_text.("OPENAGENTS_IMAGE_DIGEST"),
317 323
    secure_cookies: secure_cookies,
318 324
    https_aliases: https_aliases,
docs/runtime-configuration.md modified +1

@@ -130,6 +130,7 @@ URLs, receipts, or checked-in environment files.

130 130
| GitHub | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | Staging OAuth application credentials |
131 131
| GitHub | `GITHUB_REDIRECT_URI` | Exact HTTPS callback on `PHX_HOST` |
132 132
| GitHub | `GITHUB_OAUTH_SCOPES` | Exactly `repo`; profile identity needs no additional scope |
133
| GitHub | `OPENAGENTS_ADMIN_GITHUB_IDS` | Comma-separated immutable numeric GitHub IDs allowed to use operator surfaces; never use logins |
133 134
| GitHub | `GITHUB_TOKEN_ENCRYPTION_KEY` | Base64-encoded 32-byte staging key |
134 135
| GitHub | `GITHUB_TOKEN_ENCRYPTION_KEY_ID` | Bounded active-key identifier prefixed with `development-`, `test-`, `staging-`, or `production-` to match the runtime |
135 136
| GitHub | `GITHUB_TOKEN_DECRYPTION_KEYS_JSON` | Optional map of at most 16 same-environment prior keys used only during rewrap; omit the active ID |
lib/openagents/accounts/operator_config.ex added +34

@@ -0,0 +1,34 @@

1
defmodule OpenAgents.Accounts.OperatorConfig do
2
  @moduledoc "Parses the bounded GitHub identity allowlist for operator surfaces."
3
4
  @maximum_operators 32
5
6
  @spec parse_github_ids!(String.t() | nil) :: [pos_integer()]
7
  def parse_github_ids!(nil), do: []
8
9
  def parse_github_ids!(encoded) when is_binary(encoded) do
10
    ids =
11
      encoded
12
      |> String.split(",", trim: true)
13
      |> Enum.map(&parse_id!/1)
14
      |> Enum.uniq()
15
16
    if ids == [] or length(ids) > @maximum_operators do
17
      raise ArgumentError,
18
            "OPENAGENTS_ADMIN_GITHUB_IDS must contain between 1 and #{@maximum_operators} IDs"
19
    end
20
21
    ids
22
  end
23
24
  def parse_github_ids!(_invalid) do
25
    raise ArgumentError, "OPENAGENTS_ADMIN_GITHUB_IDS must be a comma-separated list"
26
  end
27
28
  defp parse_id!(encoded) do
29
    case Integer.parse(String.trim(encoded)) do
30
      {id, ""} when id > 0 -> id
31
      _invalid -> raise ArgumentError, "OPENAGENTS_ADMIN_GITHUB_IDS contains an invalid ID"
32
    end
33
  end
34
end
test/openagents/accounts/operator_config_test.exs added +15

@@ -0,0 +1,15 @@

1
defmodule OpenAgents.Accounts.OperatorConfigTest do
2
  use ExUnit.Case, async: true
3
4
  alias OpenAgents.Accounts.OperatorConfig
5
6
  test "parses unique immutable GitHub IDs" do
7
    assert OperatorConfig.parse_github_ids!("14167547, 42,14167547") == [14_167_547, 42]
8
  end
9
10
  test "refuses empty, textual, and nonpositive identities" do
11
    assert_raise ArgumentError, fn -> OperatorConfig.parse_github_ids!("") end
12
    assert_raise ArgumentError, fn -> OperatorConfig.parse_github_ids!("octocat") end
13
    assert_raise ArgumentError, fn -> OperatorConfig.parse_github_ids!("0") end
14
  end
15
end

This page updates live while a promote is in flight · changelog