Merge remote-tracking branch 'origin/main' into sec-mail-user-boundary
# Conflicts: # internal/provision/testdata/plan14-root-reads-chain.tsv
26 files changed,
+684
−111
README.md+7 −0cmd/console/main.go+54 −0cmd/console/sidecaraccounts_serve_test.go+55 −0cmd/console/sidecaraccounts_test.go+44 −0docs/site/cli.md+6 −0docs/superpowers/notes/census-proposal-console-brand-name.md+13 −0docs/superpowers/notes/census-proposal-email-signin.md+4 −2docs/superpowers/notes/census-proposal-sec-deps-dos-bumps.md+37 −0docs/superpowers/notes/census-proposal-sec-host-imdsv2-module.md+28 −0docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md+2 −57docs/superpowers/notes/census-proposal-sec-sidecar-create-gate.md+13 −0go.mod+11 −12go.sum+22 −34infra/modules/region-host/main.tf+13 −0infra/modules/region-host/tests/instance_metadata.tftest.hcl+49 −0internal/console/apisidecars.go+13 −1internal/console/server.go+7 −0internal/console/sidecarsgate.go+64 −0internal/console/sidecarsgate_test.go+193 −0internal/provision/plan9_b8_census_test.go+1 −1internal/provision/sec_sidecar_create_gate_census_test.go+27 −0internal/provision/testdata/plan14-root-reads-chain.tsv+4 −1internal/provision/testdata/plan14-root-reads-sec-deps-dos-bumps.tsv+3 −0internal/provision/testdata/plan14-root-reads-sec-host-imdsv2-module.tsv+5 −0internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv+3 −3internal/provision/testdata/plan14-root-reads-sec-sidecar-create-gate.tsv+6 −0
CI passed — run details
CI log
=== gate ===
.amadan/test-gate.sh
go: downloading github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.68.0
go: downloading github.com/aws/aws-sdk-go-v2 v1.44.0
go: downloading github.com/aws/aws-sdk-go-v2/service/costexplorer v1.67.5
go: downloading github.com/aws/aws-sdk-go-v2/service/s3 v1.105.0
go: downloading github.com/aws/smithy-go v1.28.1
go: downloading github.com/superfly/ltx v0.5.1
go: downloading golang.org/x/net v0.60.0
go: downloading modernc.org/sqlite v1.55.0
go: downloading github.com/aws/aws-sdk-go-v2/config v1.32.6
go: downloading github.com/aws/aws-sdk-go-v2/service/sns v1.43.0
go: downloading github.com/aws/aws-sdk-go-v2/service/route53 v1.64.1
go: downloading github.com/benbjohnson/litestream v0.5.14
go: downloading golang.org/x/crypto v0.57.0
go: downloading github.com/aws/aws-sdk-go-v2/credentials v1.19.6
go: downloading github.com/aws/aws-sdk-go-v2/service/sts v1.45.6
go: downloading golang.org/x/sys v0.48.0
go: downloading github.com/aws/aws-sdk-go-v2/service/acm v1.45.0
go: downloading github.com/aws/aws-sdk-go-v2/service/ec2 v1.324.0
go: downloading github.com/aws/aws-sdk-go-v2/service/iam v1.58.2
go: downloading github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.37.0
go: downloading github.com/oschwald/maxminddb-golang v1.13.1
go: downloading github.com/aws/aws-sdk-go-v2/service/autoscaling v1.74.0
go: downloading github.com/pion/logging v0.2.4
go: downloading github.com/pion/stun/v3 v3.1.5
go: downloading github.com/aws/aws-sdk-go-v2/service/sesv2 v1.66.6
go: downloading github.com/pion/turn/v4 v4.1.4
go: downloading github.com/aws/aws-sdk-go-v2/service/ec2 v1.324.0
go: downloading golang.org/x/crypto v0.57.0
go: downloading github.com/aws/aws-sdk-go-v2/service/ssm v1.74.0
go: downloading github.com/pierrec/lz4/v4 v4.1.22
go: downloading github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40
go: downloading github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.38
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40
go: downloading github.com/prometheus/client_golang v1.17.0
go: downloading golang.org/x/sync v0.23.0
go: downloading github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18
go: downloading github.com/aws/aws-sdk-go-v2/service/autoscaling v1.74.0
go: downloading github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40
go: downloading modernc.org/sqlite v1.55.0
go: downloading github.com/klauspost/compress v1.18.0
go: downloading github.com/pion/logging v0.2.4
go: downloading github.com/pion/stun/v3 v3.1.5
go: downloading github.com/lmittmann/tint v1.1.3
go: downloading github.com/pion/turn/v4 v4.1.4
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading github.com/aws/aws-sdk-go-v2/service/sesv2 v1.66.6
go: downloading github.com/pion/transport/v4 v4.0.2
go: downloading github.com/pion/dtls/v3 v3.1.4
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.23
go: downloading github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.14
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.31
go: downloading github.com/pierrec/lz4/v4 v4.1.22
go: downloading github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16
go: downloading github.com/aws/aws-sdk-go-v2/service/signin v1.0.4
go: downloading github.com/aws/aws-sdk-go-v2/service/sso v1.30.8
go: downloading github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12
go: downloading github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4
go: downloading golang.org/x/text v0.42.0
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19
go: downloading github.com/aws/aws-sdk-go-v2/service/signin v1.0.4
go: downloading github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16
go: downloading github.com/aws/aws-sdk-go-v2/service/sso v1.30.8
go: downloading github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12
go: downloading golang.org/x/text v0.42.0
go: downloading github.com/wlynxg/anet v0.0.5
go: downloading github.com/wlynxg/anet v0.0.5
go: downloading github.com/prometheus/client_model v0.5.0
go: downloading github.com/prometheus/common v0.45.0
go: downloading google.golang.org/protobuf v1.36.10
go: downloading github.com/prometheus/procfs v0.12.0
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/cespare/xxhash/v2 v2.3.0
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/cespare/xxhash/v2 v2.3.0
go: downloading github.com/prometheus/client_model v0.5.0
go: downloading github.com/prometheus/procfs v0.12.0
go: downloading github.com/prometheus/common v0.45.0
go: downloading google.golang.org/protobuf v1.36.10
go: downloading github.com/pion/randutil v0.1.0
go: downloading github.com/pion/randutil v0.1.0
go: downloading github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
go: downloading github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
go: downloading modernc.org/libc v1.74.1
go: downloading modernc.org/libc v1.74.1
go: downloading modernc.org/memory v1.11.0
go: downloading modernc.org/memory v1.11.0
go: downloading github.com/dustin/go-humanize v1.0.1
go: downloading github.com/google/uuid v1.6.0
go: downloading github.com/google/uuid v1.6.0
go: downloading modernc.org/mathutil v1.7.1
go: downloading modernc.org/mathutil v1.7.1
go: downloading github.com/dustin/go-humanize v1.0.1
go: downloading github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec
go: downloading github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec
=== launchctx (4 s)
ok amadan.net/carlos/platform.git/launchctx 0.011s
? amadan.net/carlos/platform.git/launchctx/controlname [no test files]
? amadan.net/carlos/platform.git/launchctx/wireid [no test files]
=== vet (46 s)
go: downloading modernc.org/sqlite v1.55.0
go: downloading github.com/aws/aws-sdk-go-v2/service/ec2 v1.324.0
go: downloading github.com/aws/aws-sdk-go-v2/service/autoscaling v1.74.0
go: downloading github.com/aws/aws-sdk-go-v2/service/ssm v1.74.0
go: downloading github.com/klauspost/compress v1.18.0
go: downloading github.com/pion/logging v0.2.4
go: downloading github.com/pion/turn/v4 v4.1.4
go: downloading github.com/pierrec/lz4/v4 v4.1.22
go: downloading github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19
go: downloading github.com/pion/dtls/v3 v3.1.4
go: downloading github.com/pion/transport/v4 v4.0.2
go: downloading golang.org/x/tools v0.49.0
go: downloading github.com/aws/aws-sdk-go-v2/service/signin v1.0.4
go: downloading github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16
go: downloading github.com/aws/aws-sdk-go-v2/service/sso v1.30.8
go: downloading github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12
go: downloading golang.org/x/text v0.42.0
go: downloading github.com/wlynxg/anet v0.0.5
go: downloading github.com/cespare/xxhash/v2 v2.3.0
go: downloading github.com/prometheus/common v0.45.0
go: downloading github.com/prometheus/client_model v0.5.0
go: downloading github.com/prometheus/procfs v0.12.0
go: downloading github.com/beorn7/perks v1.0.1
go: downloading google.golang.org/protobuf v1.36.10
go: downloading github.com/pion/randutil v0.1.0
go: downloading github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
go: downloading golang.org/x/mod v0.41.0
go: downloading modernc.org/libc v1.74.1
go: downloading modernc.org/memory v1.11.0
go: downloading github.com/dustin/go-humanize v1.0.1
go: downloading github.com/google/uuid v1.6.0
go: downloading modernc.org/mathutil v1.7.1
go: downloading github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec
=== gofmt (1 s)
gofmt -l .: clean
=== rest (199 s)
? github.com/carlosframework/platform/cmd/changelog [no test files]
ok github.com/carlosframework/platform/cmd/console 3.330s
ok github.com/carlosframework/platform/cmd/moveprobe 11.841s
ok github.com/carlosframework/platform/cmd/zddprobe 0.689s
ok github.com/carlosframework/platform/internal/activator 58.957s
ok github.com/carlosframework/platform/internal/agentsock 0.027s
ok github.com/carlosframework/platform/internal/alerting 0.008s
ok github.com/carlosframework/platform/internal/analytics 0.023s
ok github.com/carlosframework/platform/internal/appconfig 2.587s
ok github.com/carlosframework/platform/internal/apperr 0.119s
ok github.com/carlosframework/platform/internal/applog 0.272s
ok github.com/carlosframework/platform/internal/awsinv 0.008s
ok github.com/carlosframework/platform/internal/boxchoice 0.006s
ok github.com/carlosframework/platform/internal/boxid 0.819s
? github.com/carlosframework/platform/internal/boxid/boxidtest [no test files]
? github.com/carlosframework/platform/internal/boxid/internal/signtest [no test files]
ok github.com/carlosframework/platform/internal/boxvolumes 0.008s
ok github.com/carlosframework/platform/internal/caps 0.015s
ok github.com/carlosframework/platform/internal/certwarm 0.005s
ok github.com/carlosframework/platform/internal/cgroup 0.758s
ok github.com/carlosframework/platform/internal/changelog 0.467s
ok github.com/carlosframework/platform/internal/claimactivation 1.084s
ok github.com/carlosframework/platform/internal/clamd 0.059s
? github.com/carlosframework/platform/internal/cliflag [no test files]
? github.com/carlosframework/platform/internal/controlname [no test files]
? github.com/carlosframework/platform/internal/controlsock [no test files]
ok github.com/carlosframework/platform/internal/dbdoor 7.827s
ok github.com/carlosframework/platform/internal/dbdoor/dbdoortest 0.015s
ok github.com/carlosframework/platform/internal/dbquery 0.011s
ok github.com/carlosframework/platform/internal/dbreads 2.143s
? github.com/carlosframework/platform/internal/dbreads/dbreadstest [no test files]
ok github.com/carlosframework/platform/internal/dbverify 0.008s
ok github.com/carlosframework/platform/internal/dnsrec 0.005s
ok github.com/carlosframework/platform/internal/docsite 0.011s
ok github.com/carlosframework/platform/internal/domains 2.596s
ok github.com/carlosframework/platform/internal/doorkey 0.012s
ok github.com/carlosframework/platform/internal/economics 0.010s
ok github.com/carlosframework/platform/internal/edge 49.598s
? github.com/carlosframework/platform/internal/edgeproof [no test files]
ok github.com/carlosframework/platform/internal/edgesfile 0.008s
ok github.com/carlosframework/platform/internal/fleetapi 0.138s
ok github.com/carlosframework/platform/internal/gate 1.656s
ok github.com/carlosframework/platform/internal/genselect 0.015s
ok github.com/carlosframework/platform/internal/geo 0.012s
ok github.com/carlosframework/platform/internal/geoip 0.029s
ok github.com/carlosframework/platform/internal/historyreadback 0.155s
ok github.com/carlosframework/platform/internal/host 146.915s
ok github.com/carlosframework/platform/internal/hostactors 6.374s
ok github.com/carlosframework/platform/internal/hostactors/awsmembership 0.005s
ok github.com/carlosframework/platform/internal/hostbox 3.263s
ok github.com/carlosframework/platform/internal/hostclaim 0.751s
ok github.com/carlosframework/platform/internal/hostenv 0.005s
ok github.com/carlosframework/platform/internal/hostevidence 0.010s
ok github.com/carlosframework/platform/internal/hostexec 5.714s
ok github.com/carlosframework/platform/internal/hostfence 0.037s
ok github.com/carlosframework/platform/internal/hostfiles 10.441s
ok github.com/carlosframework/platform/internal/hostfloor 0.019s
ok github.com/carlosframework/platform/internal/hostguard 1.037s
ok github.com/carlosframework/platform/internal/hostops 2.019s
? github.com/carlosframework/platform/internal/hostops/fencerelease [no test files]
ok github.com/carlosframework/platform/internal/hostops/internal/fencerelease 0.003s
ok github.com/carlosframework/platform/internal/hostquarantine 0.004s
ok github.com/carlosframework/platform/internal/hostrelocate 0.640s
ok github.com/carlosframework/platform/internal/hostresolve 0.202s
ok github.com/carlosframework/platform/internal/hostwrite 0.002s
ok github.com/carlosframework/platform/internal/infraview 0.003s
ok github.com/carlosframework/platform/internal/instancectl 5.249s
ok github.com/carlosframework/platform/internal/instanceexec 2.835s
ok github.com/carlosframework/platform/internal/instancefs 0.015s
ok github.com/carlosframework/platform/internal/instancekey 0.004s
ok github.com/carlosframework/platform/internal/instanceupdates 0.013s
ok github.com/carlosframework/platform/internal/layout 1.800s
ok github.com/carlosframework/platform/internal/lbhealth 0.004s
ok github.com/carlosframework/platform/internal/lease 10.096s
ok github.com/carlosframework/platform/internal/lease/leasetest 0.623s
ok github.com/carlosframework/platform/internal/ledger 0.030s
ok github.com/carlosframework/platform/internal/mail 0.220s
ok github.com/carlosframework/platform/internal/maintenance 0.107s
ok github.com/carlosframework/platform/internal/maintenanceexec 5.570s
ok github.com/carlosframework/platform/internal/maintenanceguard 0.117s
ok github.com/carlosframework/platform/internal/metrics 0.006s
ok github.com/carlosframework/platform/internal/moves 29.744s
ok github.com/carlosframework/platform/internal/moves/archive 1.030s
ok github.com/carlosframework/platform/internal/objstore 0.322s
ok github.com/carlosframework/platform/internal/ops 0.239s
ok github.com/carlosframework/platform/internal/park 0.258s
ok github.com/carlosframework/platform/internal/plans 0.008s
ok github.com/carlosframework/platform/internal/privatefile 0.007s
ok github.com/carlosframework/platform/internal/provision 51.348s
ok github.com/carlosframework/platform/internal/publicrelease 0.013s
? github.com/carlosframework/platform/internal/regionname [no test files]
ok github.com/carlosframework/platform/internal/registry 5.977s
ok github.com/carlosframework/platform/internal/relay 7.203s
ok github.com/carlosframework/platform/internal/release 34.202s
ok github.com/carlosframework/platform/internal/rollout 4.195s
ok github.com/carlosframework/platform/internal/rolloutwords 0.003s
ok github.com/carlosframework/platform/internal/rootaws 0.009s
ok github.com/carlosframework/platform/internal/rootparticipation 0.062s
ok github.com/carlosframework/platform/internal/rootstate 0.035s
ok github.com/carlosframework/platform/internal/runs 0.593s
ok github.com/carlosframework/platform/internal/s3compat 0.008s
ok github.com/carlosframework/platform/internal/safefile 0.009s
ok github.com/carlosframework/platform/internal/schedule 2.837s
ok github.com/carlosframework/platform/internal/seeds 0.014s
ok github.com/carlosframework/platform/internal/sim/engines 2.938s
ok github.com/carlosframework/platform/internal/sim/hostlife 10.661s
ok github.com/carlosframework/platform/internal/sim/litestreamfacts 4.600s
ok github.com/carlosframework/platform/internal/status 0.067s
ok github.com/carlosframework/platform/internal/steering 0.006s
ok github.com/carlosframework/platform/internal/stopevidence 0.304s
ok github.com/carlosframework/platform/internal/svcrole 0.003s
ok github.com/carlosframework/platform/internal/telemetry 0.005s
ok github.com/carlosframework/platform/internal/termfence 0.007s
ok github.com/carlosframework/platform/internal/tunnelclient 0.004s
ok github.com/carlosframework/platform/internal/unixdial 0.004s
ok github.com/carlosframework/platform/internal/updatewords 0.005s
ok github.com/carlosframework/platform/internal/usage 0.006s
ok github.com/carlosframework/platform/internal/version 0.002s
ok github.com/carlosframework/platform/internal/vet 1.709s
=== internal_console@0 (83 s)
ok github.com/carlosframework/platform/internal/console 81.312s
=== internal_console@1 (50 s)
ok github.com/carlosframework/platform/internal/console 47.971s
=== internal_console@2 (44 s)
ok github.com/carlosframework/platform/internal/console 42.302s
=== internal_console@3 (46 s)
ok github.com/carlosframework/platform/internal/console 44.829s
=== internal_console@4 (47 s)
ok github.com/carlosframework/platform/internal/console 45.829s
=== internal_console@5 (48 s)
ok github.com/carlosframework/platform/internal/console 46.636s
=== internal_console@6 (45 s)
ok github.com/carlosframework/platform/internal/console 43.553s
=== internal_console@7 (52 s)
ok github.com/carlosframework/platform/internal/console 49.988s
=== root@0 (112 s)
ok github.com/carlosframework/platform 110.462s
=== root@1 (84 s)
ok github.com/carlosframework/platform 82.923s
=== root@2 (96 s)
ok github.com/carlosframework/platform 94.435s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 50659490 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
minio: cached, digest verified
mc: cached, digest verified
minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
buckets and scoped user ready
=== RUN TestDeliveredContractMinio
=== RUN TestDeliveredContractMinio/put
=== RUN TestDeliveredContractMinio/get_round-trips_the_body
=== RUN TestDeliveredContractMinio/list_shows_the_key
=== RUN TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN TestDeliveredContractMinio/delete_removes_it
=== RUN TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
--- PASS: TestDeliveredContractMinio/put (0.00s)
--- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
--- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
--- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
--- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
--- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
--- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok github.com/carlosframework/platform/internal/objstore 0.026s
=== RUN TestT4StorageProbeMinio
2026/10/24 21:16:13 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E193BE81D507E2, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok github.com/carlosframework/platform/internal/host 0.022s
=== RUN TestS3StoreMinioContract
=== RUN TestS3StoreMinioContract/conditional_get_and_etag_listing
=== RUN TestS3StoreMinioContract/bump_through_every_mutation_path
--- PASS: TestS3StoreMinioContract (0.03s)
--- PASS: TestS3StoreMinioContract/conditional_get_and_etag_listing (0.01s)
--- PASS: TestS3StoreMinioContract/bump_through_every_mutation_path (0.02s)
PASS
ok github.com/carlosframework/platform/internal/release 0.031s
Bucket created successfully `local/dbf-deploy`.
Bucket created successfully `local/dbf-data-ie`.
Bucket created successfully `local/dbf-data-uk`.
local/dbf-deploy versioning is enabled
Created policy `dbf-console` successfully.
Created policy `dbf-ukbox` successfully.
Added user `dbfconsole` successfully.
Added user `dbfukbox` successfully.
Attached Policies: [dbf-console]
To User: dbfconsole
Attached Policies: [dbf-ukbox]
To User: dbfukbox
=== RUN TestDBForwardMinio
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:16:15 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 21:16:15 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 21:16:15 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 21:16:15 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 21:16:15 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/24 21:16:15 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 21:16:15 console: could not notify member@example.com of a CLI token issue: keymail: lookup member@example.com: keymail: GET /api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A43381: could not determine target mailbox
agent.go:135: dbreads: dbr_r4ljijgxbxt7denspmsfmgmq6q: planned 3 files (1544 bytes) and restored to 3 in 10ms
agent.go:135: dbreads: dbr_r4ljijgxbxt7denspmsfmgmq6q: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 21:16:16 console: db read dbr_r4ljijgxbxt7denspmsfmgmq6q: relayed 1 chunks (102 bytes) in 1.003s, ended done
agent.go:135: dbreads: dbr_vhqrkpbmkxhrway4irljmpv3na: planned 1 files (720 bytes) and restored to 1 in 8ms
agent.go:135: dbreads: dbr_vhqrkpbmkxhrway4irljmpv3na: output 5 bytes in 1 confirmed chunks in 2ms
2026/10/24 21:16:17 console: db read dbr_vhqrkpbmkxhrway4irljmpv3na: relayed 1 chunks (102 bytes) in 1.003s, ended done
agent.go:135: dbreads: dbr_xqcteayj6kn3d63irgh7jh6cfq: planned 3 files (1544 bytes) and held through 3 in 9ms
agent.go:135: dbreads: dbr_xqcteayj6kn3d63irgh7jh6cfq: output 1544 bytes in 1 confirmed chunks in 1ms
2026/10/24 21:16:18 console: db read dbr_xqcteayj6kn3d63irgh7jh6cfq: relayed 1 chunks (1641 bytes) in 1.004s, ended done
=== RUN TestDBForwardMinio/by_disconnect
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_eb4bcuniiw72pmiffgo4wvgf6a: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 21:16:18 console: db read dbr_eb4bcuniiw72pmiffgo4wvgf6a: relayed 0 chunks (0 bytes) in 23ms, ended client gone
agent.go:135: dbreads: dbr_eb4bcuniiw72pmiffgo4wvgf6a: output 5 bytes in 1 confirmed chunks in 104ms
=== RUN TestDBForwardMinio/by_timeout
=== NAME TestDBForwardMinio
agent.go:135: dbreads: dbr_tz3dxlyaon7zbowfxi32ocxfza: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 21:16:38 console: db read dbr_tz3dxlyaon7zbowfxi32ocxfza: relayed 0 chunks (0 bytes) in 19.148s, ended no-result
agent.go:135: dbreads: dbr_tz3dxlyaon7zbowfxi32ocxfza: output 5 bytes in 1 confirmed chunks in 19.123s
--- PASS: TestDBForwardMinio (22.58s)
--- PASS: TestDBForwardMinio/by_disconnect (0.22s)
--- PASS: TestDBForwardMinio/by_timeout (19.26s)
PASS
ok github.com/carlosframework/platform/internal/console 22.597s
minio contract: PASS
diff --git a/README.md b/README.md| index 14c9947fa..3cf801394 100644 |
| --- a/README.md |
| +++ b/README.md |
| @@ -660,6 +660,13 @@ publish an inflated instance count. |
| `carlos system geoip publish` instead (docs/site/cli.md, "Platform GeoIP"). |
| Uploads are staged in `geoip-staging/` next to `CONSOLE_DB`, which the |
| console clears at startup. |
| +- `CARLOS_SIDECAR_ACCOUNTS`: comma-separated account sqids (not names) that |
| + may still enable and create native sidecars. Set, every other account gets |
| + 403 `sidecars-paused` from `carlos sidecars enable` and `carlos sidecars |
| + create`. List, get, wake and delete still work, and existing sidecars keep |
| + running. Native sidecars run on the flat tier, outside the nested tenants' |
| + isolation, until nested sidecars land (discussion #370). **Unset or empty = |
| + no gate.** An entry that is not a sqid stops the console at boot. |
| **Canonical-host caveat**, stated honestly: an app that pins its own host |
| (e.g. keymail's `--host keymail.dev`) will redirect or 404 on its platform |
diff --git a/cmd/console/main.go b/cmd/console/main.go| index e3b30551f..d09b991b3 100644 |
| --- a/cmd/console/main.go |
| +++ b/cmd/console/main.go |
| @@ -51,6 +51,14 @@ |
| // CARLOS_TEMPORARY_APPS=1 turns on temporary apps (carlos deploy |
| // --temporary); off by default, see the temporary static sites rollout. |
| // |
| +// CARLOS_SIDECAR_ACCOUNTS (comma-separated account sqids) pauses native |
| +// sidecars for every account it does not name: enabling sidecars and |
| +// creating one answer 403 sidecars-paused, while list, get, wake and delete |
| +// keep working and existing sidecars keep running. Native sidecars run on |
| +// the flat tier, outside the nested tenants' isolation, until discussion |
| +// #370 lands. Unset or empty is no gate. An entry that is not a sqid, or a |
| +// value that names no sqid at all, stops the console at startup. |
| +// |
| // Tenant email (task 17) reads CARLOS_MAIL_OPERATORS (comma-separated |
| // addresses; narrows the fleet-operator bit further for the pause/cap |
| // door only — see mailOperators' doc comment), CARLOS_MAIL_REGION, |
| @@ -133,6 +141,7 @@ import ( |
| "github.com/carlosframework/platform/internal/appconfig" |
| "github.com/carlosframework/platform/internal/console" |
| "github.com/carlosframework/platform/internal/dnsrec" |
| + "github.com/carlosframework/platform/internal/domains" |
| "github.com/carlosframework/platform/internal/geo" |
| "github.com/carlosframework/platform/internal/geoip" |
| "github.com/carlosframework/platform/internal/lease" |
| @@ -317,6 +326,12 @@ func serve() { |
| } else { |
| log.Print("console: CARLOS_RELEASE_KEY unset — promotions disabled, running read-only") |
| } |
| + // Before anything is opened: a typo in the allowlist must stop the |
| + // console rather than quietly pause, or unpause, the wrong accounts. |
| + sidecarAccts, err := sidecarAccounts(os.Getenv) |
| + if err != nil { |
| + log.Fatalf("console: %v", err) |
| + } |
| hostClaim := hostClaimEnabled(os.Getenv) |
| st := openServeDB(hostClaim) |
| storeProvisioner, storeDeployment := storeBackend() |
| @@ -490,6 +505,9 @@ func serve() { |
| // owners (discussion #176). Unset is the owner-only console. |
| SystemOperators: systemOperators(), |
| PlatformDomain: platformDomain(), |
| + // The sidecar pause (internal/console/sidecarsgate.go). Unset is no |
| + // gate; set, only these accounts may enable or create a sidecar. |
| + SidecarAccounts: sidecarAccts, |
| } |
| // The Phase 1 moves switch (CARLOS_MOVES_PHASE1, off by default, index |
| // D13): applied after the literal so a test can prove it reaches the |
| @@ -919,6 +937,42 @@ func systemOperators() []string { |
| return out |
| } |
| +// sidecarAccounts parses CARLOS_SIDECAR_ACCOUNTS (comma-separated account |
| +// sqids): the accounts that may still enable and create native sidecars |
| +// while every other account is paused (internal/console/sidecarsgate.go, |
| +// discussion #370). Each entry is trimmed and lower-cased, and must be a |
| +// sqid (domains.ValidSqid), never an account name: "ops" is not one, and a |
| +// name that matched nothing would pause the very account it meant to keep. |
| +// An invalid entry is an error for serve to stop on. |
| +// |
| +// Unset or the empty string is nil, and nil is no gate: every account may |
| +// enable and create sidecars, exactly as before the list existed. This is |
| +// the opposite direction to the operator lists above on purpose, so a |
| +// deployment that never sets it is unaffected. Any other value that names |
| +// no sqid at all ("," or " , ") is an error too: it reads as a list, and |
| +// parsing it to nil would quietly turn the gate off. |
| +func sidecarAccounts(getenv func(string) string) ([]string, error) { |
| + raw := getenv("CARLOS_SIDECAR_ACCOUNTS") |
| + if raw == "" { |
| + return nil, nil |
| + } |
| + var out []string |
| + for _, a := range strings.Split(raw, ",") { |
| + a = strings.ToLower(strings.TrimSpace(a)) |
| + if a == "" { |
| + continue |
| + } |
| + if !domains.ValidSqid(a) { |
| + return nil, fmt.Errorf("CARLOS_SIDECAR_ACCOUNTS: %q is not an account sqid (the account's id, e.g. bab, never its name)", a) |
| + } |
| + out = append(out, a) |
| + } |
| + if len(out) == 0 { |
| + return nil, fmt.Errorf("CARLOS_SIDECAR_ACCOUNTS: %q names no account sqid; unset it for no gate, or list at least one sqid", raw) |
| + } |
| + return out, nil |
| +} |
| + |
| // platformDomain reads CARLOS_PLATFORM_DOMAIN — the domain the relay's own |
| // hostnames hang off (relay.<region>.<domain>), which an enabled app is |
| // handed as its ICE server URLs. |
diff --git a/cmd/console/sidecaraccounts_serve_test.go b/cmd/console/sidecaraccounts_serve_test.go| new file mode 100644 |
| index 000000000..066138fba |
| --- /dev/null |
| +++ b/cmd/console/sidecaraccounts_serve_test.go |
| @@ -0,0 +1,55 @@ |
| +package main_test |
| + |
| +import ( |
| + "context" |
| + "os" |
| + "os/exec" |
| + "path/filepath" |
| + "strings" |
| + "testing" |
| + "time" |
| +) |
| + |
| +// TestServeStopsOnABadSidecarAccount: serve reads CARLOS_SIDECAR_ACCOUNTS |
| +// and stops before it opens anything on an entry that is not a sqid, or on a |
| +// value set to a list that names no sqid at all ("," would otherwise parse |
| +// to nil and quietly turn the gate off). The timeout is the failure mode it |
| +// guards: a serve that ignored the variable would carry on and listen. |
| +func TestServeStopsOnABadSidecarAccount(t *testing.T) { |
| + bin := buildConsole(t) |
| + for _, c := range []struct{ val, want string }{ |
| + {"bab,ops", `"ops"`}, |
| + {",", "names no account sqid"}, |
| + {" , ", "names no account sqid"}, |
| + } { |
| + tmp := t.TempDir() |
| + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
| + cmd := exec.CommandContext(ctx, bin, "serve") |
| + cmd.Dir = tmp |
| + cmd.Env = append(os.Environ(), |
| + "CONSOLE_ORIGIN=http://127.0.0.1:1", |
| + "CONSOLE_ADDR=127.0.0.1:0", |
| + "CONSOLE_DB="+filepath.Join(tmp, "console.db"), |
| + // 32 zero bytes: a well-formed Ed25519 public key, so serve gets |
| + // past the key check to the variable under test. |
| + "CARLOS_RELEASE_PUBKEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", |
| + "CARLOS_RELEASE_KEY=", |
| + "CARLOS_SIDECAR_ACCOUNTS="+c.val, |
| + ) |
| + out, err := cmd.CombinedOutput() |
| + timedOut := ctx.Err() != nil |
| + cancel() |
| + if timedOut { |
| + t.Fatalf("CARLOS_SIDECAR_ACCOUNTS=%q: serve did not stop: %s", c.val, out) |
| + } |
| + if err == nil { |
| + t.Fatalf("CARLOS_SIDECAR_ACCOUNTS=%q: serve exited 0: %s", c.val, out) |
| + } |
| + if !strings.Contains(string(out), "CARLOS_SIDECAR_ACCOUNTS") || !strings.Contains(string(out), c.want) { |
| + t.Fatalf("CARLOS_SIDECAR_ACCOUNTS=%q: the refusal does not name the variable and %s: %s", c.val, c.want, out) |
| + } |
| + if _, err := os.Stat(filepath.Join(tmp, "console.db")); err == nil { |
| + t.Fatalf("CARLOS_SIDECAR_ACCOUNTS=%q: serve opened its database before refusing", c.val) |
| + } |
| + } |
| +} |
diff --git a/cmd/console/sidecaraccounts_test.go b/cmd/console/sidecaraccounts_test.go| new file mode 100644 |
| index 000000000..cba72c2bd |
| --- /dev/null |
| +++ b/cmd/console/sidecaraccounts_test.go |
| @@ -0,0 +1,44 @@ |
| +package main |
| + |
| +import ( |
| + "slices" |
| + "testing" |
| +) |
| + |
| +// CARLOS_SIDECAR_ACCOUNTS reaches Config.SidecarAccounts through |
| +// sidecarAccounts: trimmed, lower-cased, each a sqid. Unset and the empty |
| +// string are nil, which is no gate; any entry that is not a sqid, and any |
| +// other value that names no sqid, is an error serve stops on (TestServeStopsOnABadSidecarAccount runs that through the binary). |
| +func TestSidecarAccountsParsesTheList(t *testing.T) { |
| + for _, tc := range []struct { |
| + val string |
| + want []string |
| + wantErr bool |
| + }{ |
| + {"", nil, false}, |
| + {" ", nil, true}, // set, but names no sqid: refused, never "no gate" |
| + {",", nil, true}, // the same, and the review's case |
| + {" , ", nil, true}, // ditto |
| + {"bab", []string{"bab"}, false}, |
| + {" BAB , cdef ,", []string{"bab", "cdef"}, false}, |
| + {"bab,ops", nil, true}, // a name, not a sqid: o is not in the alphabet |
| + {"bab,ab", nil, true}, // too short |
| + {"bab,b-b", nil, true}, // not a letter |
| + {"bab;cdef", nil, true}, // wrong separator reads as one bad entry |
| + {"x1234567890abcdefg", nil, true}, |
| + } { |
| + got, err := sidecarAccounts(func(k string) string { |
| + if k != "CARLOS_SIDECAR_ACCOUNTS" { |
| + t.Fatalf("read %s", k) |
| + } |
| + return tc.val |
| + }) |
| + if (err != nil) != tc.wantErr { |
| + t.Errorf("CARLOS_SIDECAR_ACCOUNTS=%q: err %v, want error %v", tc.val, err, tc.wantErr) |
| + continue |
| + } |
| + if !slices.Equal(got, tc.want) { |
| + t.Errorf("CARLOS_SIDECAR_ACCOUNTS=%q: %v, want %v", tc.val, got, tc.want) |
| + } |
| + } |
| +} |
diff --git a/docs/site/cli.md b/docs/site/cli.md| index 0a8e384f2..3d0dd287a 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -1016,6 +1016,12 @@ it back from, so `carlos sidecars ls` shows the build its box last started |
| for each sidecar, beside the version its channel points at. One that has not |
| started on any box reads as unknown rather than as current. |
| +A console operator can pause sidecars for every account not named in |
| +`CARLOS_SIDECAR_ACCOUNTS`, a comma-separated list of account sqids. A paused |
| +account gets a 403 with the code `sidecars-paused` from `enable` and |
| +`create`. Its existing sidecars keep running, and `ls`, `wake` and `rm` still |
| +work. Unset, nothing is paused. |
| + |
| ### carlos steering |
| Decides whether one instance's DNS answer varies by where the request comes |
diff --git a/docs/superpowers/notes/census-proposal-console-brand-name.md b/docs/superpowers/notes/census-proposal-console-brand-name.md| new file mode 100644 |
| index 000000000..e2e8ccd7d |
| --- /dev/null |
| +++ b/docs/superpowers/notes/census-proposal-console-brand-name.md |
| @@ -0,0 +1,13 @@ |
| +# Census: console brand name |
| + |
| +Status: APPROVED by Paul (census owner), 2026-10-24, recorded on #280 with |
| +main at 65ca4aca. Rows keep `rev3=PROPOSED-console-brand-name` (no flip under |
| +#369). |
| + |
| +Overlay `internal/provision/testdata/plan14-root-reads-console-brand-name.tsv`, |
| +landed with 5f341f3. The console's chrome and new-account welcome name the |
| +deployment from `CARLOS_CONSOLE_NAME`. Three embedded templates change text |
| +only (`apps.html`, `home.html`, `layout.html`), and the `internal/console` and |
| +`internal/console/templates` envelopes are re-pinned. No read, file or record |
| +write is added. The units run in the console (`cmd/console serve` under |
| +`console.service`, user carlos-console). |
diff --git a/docs/superpowers/notes/census-proposal-email-signin.md b/docs/superpowers/notes/census-proposal-email-signin.md| index f1f186c70..c32d7eb3a 100644 |
| --- a/docs/superpowers/notes/census-proposal-email-signin.md |
| +++ b/docs/superpowers/notes/census-proposal-email-signin.md |
| @@ -1,7 +1,9 @@ |
| # Census: console email sign-in |
| -Status: PROPOSED. Every row carries `rev3=PROPOSED-email-signin`. The ruling |
| -is asked of the census owner on #280. |
| +Status: APPROVED by Paul (census owner), 2026-10-24, recorded on #280 with |
| +main at 65ca4aca. The rows keep `rev3=PROPOSED-email-signin`: under the #369 |
| +process nothing is flipped, and later overlays re-froze units at these |
| +dispositions, so a flip would move their fingerprints. |
| Branch `email-signin`, cut from main at 4d6f0e6c. Design: |
| `docs/superpowers/specs/2026-10-21-console-email-signin-design.md`. |
diff --git a/docs/superpowers/notes/census-proposal-sec-deps-dos-bumps.md b/docs/superpowers/notes/census-proposal-sec-deps-dos-bumps.md| new file mode 100644 |
| index 000000000..97d2e1f04 |
| --- /dev/null |
| +++ b/docs/superpowers/notes/census-proposal-sec-deps-dos-bumps.md |
| @@ -0,0 +1,37 @@ |
| +# Census approval: sec-deps-dos-bumps |
| + |
| +Ruling `census-ruling-sec-deps-dos-bumps`. Every change below keeps the disposition it already has, so it needs an approval, not a decision. |
| + |
| +Envelopes re-pinned: `.`. |
| + |
| +Seal: `945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a` → `945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a` (unchanged; loaded after sec-host-imdsv2-module, re-chained 2026-10-24 when that merged as ef156d3). |
| + |
| +Overlay: `internal/provision/testdata/plan14-root-reads-sec-deps-dos-bumps.tsv`, appended to `internal/provision/testdata/plan14-root-reads-chain.tsv`. |
| + |
| +## Branch context |
| + |
| +Branch `sec-deps-dos-bumps` bumps three modules that govulncheck reports as |
| +reachable on main: |
| + |
| +| module | main | branch | findings | |
| +|---|---|---|---| |
| +| `golang.org/x/net` | v0.56.0 | v0.60.0 | GO-2026-6603, -6610, -6611, -6612, -6617 (HTTP/2 server and transport DoS) | |
| +| `golang.org/x/text` | v0.38.0 | v0.42.0 | GO-2026-5970 (idna infinite loop, reached from the edge's TLS SNI handling) | |
| +| `github.com/pion/stun/v3` | v3.0.1 | v3.1.5 | GO-2026-6163 (panic on a malformed XOR-MAPPED-ADDRESS, reached from the relay's TURN server) | |
| + |
| +x/text lands at v0.42.0, not v0.39.0, because x/net v0.60.0 requires it. The |
| +same requirement moves x/crypto (v0.57.0), x/sys (v0.48.0), x/sync (v0.23.0), |
| +x/mod (v0.41.0) and x/tools (v0.49.0), and raises go.mod's `go` directive from |
| +`1.26` to `1.26.0`. stun v3.1.5 moves pion/dtls/v3 to v3.1.4 and |
| +pion/transport/v4 to v4.0.2, and drops pion/transport/v3. pion/turn/v4 stays at |
| +v4.1.4. |
| + |
| +The change is go.mod and go.sum only. No Go source file changes, so no read is |
| +added, removed or changed, no census unit's digest moves, and the legacy seal |
| +does not move. The only census effect is the envelope re-pin of `.` above. |
| +The record-write census (9B8) and claim routes are unchanged. |
| + |
| +go.mod meets `go-1.26.10-emudhra`, which raises the same `go` directive to |
| +`1.26.10` and pins `.` in its own overlay. Whichever lands second takes |
| +`1.26.10` and re-pins `.` once more (rerun `make census-propose` with the same |
| +NAME). |
diff --git a/docs/superpowers/notes/census-proposal-sec-host-imdsv2-module.md b/docs/superpowers/notes/census-proposal-sec-host-imdsv2-module.md| new file mode 100644 |
| index 000000000..37512514f |
| --- /dev/null |
| +++ b/docs/superpowers/notes/census-proposal-sec-host-imdsv2-module.md |
| @@ -0,0 +1,28 @@ |
| +# Census approval: sec-host-imdsv2-module |
| + |
| +Ruling `census-ruling-sec-host-imdsv2-module`. Every change below keeps the disposition it already has, so it needs an approval, not a decision. |
| + |
| +| unit | change | read sites | what changed | |
| +|---|---|---|---| |
| +| `infra/modules/region-host/main.tf` `external` | body | unchanged (1) | aws_instance.host gains metadata_options (IMDSv2 required, hop limit 1, endpoint enabled), matching scanner-host; tofu configuration only, run on an operator's machine; no read, file or record write is added; applies in place on a running instance with no restart | |
| + |
| +Envelopes re-pinned: `infra/modules/region-host`. |
| + |
| +Seal: `09e20ea4bbc534710334e632e903878656dabfb5817d4d96c6e7c56508172148` → `945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a`. |
| + |
| +Overlay: `internal/provision/testdata/plan14-root-reads-sec-host-imdsv2-module.tsv`, appended to `internal/provision/testdata/plan14-root-reads-chain.tsv`. |
| + |
| +## Branch context |
| + |
| +The branch `sec-host-imdsv2-module` adds a `metadata_options` block to |
| +`aws_instance.host` in `infra/modules/region-host/main.tf` (IMDSv2 required, |
| +hop limit 1, endpoint enabled), matching scanner-host, and a plan-time tofu |
| +test under `infra/modules/region-host/tests/`. The record-write census does |
| +not change: no record write is added or moved. |
| + |
| +The new `tests/instance_metadata.tftest.hcl` is outside the census: the scan's |
| +extension list excludes `.tftest.hcl`, and envelopes group by immediate |
| +directory, so the module hash covers only the `.tf` files. |
| + |
| +Executing identity: tofu on an operator's machine with the operator's AWS |
| +credentials; no box unit runs the file and no binary embeds it. |
diff --git a/docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md b/docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md| index 08fcb5472..d3205caf6 100644 |
| --- a/docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md |
| +++ b/docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md |
| @@ -4,65 +4,10 @@ Ruling `census-ruling-sec-mail-user-boundary`. Every change below keeps the disp |
| | unit | change | read sites | what changed | |
| |---|---|---|---| |
| -| `cmd/console/main.go` `serve` | body | unchanged (3) | reads CARLOS_MAIL_USER_BOUNDARY (an environment variable, not a file) and log.Fatals when the mail region is set without a valid boundary ARN; passes it to mail.NewAWSProvisioner through a closure; with CARLOS_MAIL_BOUNDARY_SWEEP=1 starts sweepMailBoundaries (IAM calls only, refused under CARLOS_HOST_CLAIM before any backend call); no file or bucket read is added, the same three /data literals moved | |
| +| `cmd/console/main.go` `serve` | body | unchanged (3) | reads CARLOS_MAIL_USER_BOUNDARY and passes it to mail.NewAWSProvisioner; runs the one-shot boundary sweep behind CARLOS_MAIL_BOUNDARY_SWEEP=1, refused under CARLOS_HOST_CLAIM | |
| Envelopes re-pinned: `cmd/console`, `internal/mail`. |
| -Seal: `09e20ea4bbc534710334e632e903878656dabfb5817d4d96c6e7c56508172148` → `0704f610da881f419f2bc7138a69429c1ceeb1d220b5de24cfda9c0ab91aea02`. |
| +Seal: `0ca3baafa26f59c0b7d8c6d6a39237d074afb71f885b7fbd50debebab5bf89f2` → `b64b923281b55c7309e5ecb440cbc0869127e43b6f963849eb30257abb9fba6c`. |
| Overlay: `internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv`, appended to `internal/provision/testdata/plan14-root-reads-chain.tsv`. |
| - |
| -## Branch context |
| - |
| -The console's AWS role may call `iam:CreateUser`, `iam:PutUserPolicy` and |
| -`iam:CreateAccessKey` on `user/carlos-mail/*`, and `internal/mail`'s AWS |
| -provisioner created those users with no permissions boundary. Anyone able to |
| -act as the role could create a user there, give it an administrator inline |
| -policy and mint it a key. Object storage closed the same gap with |
| -`CARLOS_STORE_USER_BOUNDARY`; this branch mirrors it for mail. |
| - |
| -- `mail.NewAWSProvisioner` takes a boundary ARN and refuses an empty or |
| - malformed one before any AWS call. `EnsureUser` passes it to `CreateUser` |
| - as `PermissionsBoundary`, and refuses to act without one. |
| -- When `CreateUser` reports the user exists, `EnsureUser` reads the user's |
| - boundary from the `GetUser` it already made. If it is missing or |
| - different, `PutUserPermissionsBoundary` sets the configured one before |
| - `PutUserPolicy` runs. Nothing calls `DeleteUserPermissionsBoundary`. |
| -- `cmd/console serve` reads `CARLOS_MAIL_USER_BOUNDARY` and stops with |
| - `log.Fatal` (through `mailUserBoundary`) when `CARLOS_MAIL_REGION` is set |
| - and the boundary is empty or not a customer managed policy ARN |
| - (`arn:<partition>:iam::<12 digits>:policy/<path><name>`, IAM's name |
| - characters only, no control characters). |
| -- Astra's review round: `MintKey` also reads the user and backfills the |
| - boundary before `CreateAccessKey`, because rotation mints without |
| - `EnsureUser`. And `CARLOS_MAIL_BOUNDARY_SWEEP=1` runs |
| - `sweepMailBoundaries` once at console start: `iam:ListUsers` under |
| - `/carlos-mail/`, `GetUser` for each, `PutUserPermissionsBoundary` where it |
| - is missing or different, and a last log line saying all N users are |
| - bounded or which are not. IAM only, no file or bucket read. Refused under |
| - `CARLOS_HOST_CLAIM` before any backend call (Astra round 2). |
| - |
| -None of this runs as root. The changed code runs in the console |
| -(`cmd/console serve` under `console.service`, user `carlos-console`). The |
| -root `carlos` binary links neither `internal/console` nor `internal/mail` |
| -(`TestRootBinaryLinksNoConsoleOrMail`). No root read is added. |
| - |
| - |
| -Envelopes re-pinned: `cmd/console` (serve, mailUserBoundary, sweepMailBoundaries, doc comments, tests) and `internal/mail` (aws.go boundary handling, new boundarysweep.go, new aws_iam_test.go). |
| - |
| -## Claim authority census (TestClaimAuthoritySourceCensus) |
| - |
| -`internal/console/testdata/claim-authority-sites.txt` gains one line, 151 to |
| -152 calls: |
| - |
| - ../../cmd/console/main.go|serve|mail.NewAWSProvisioner(ctx, region, mailBoundary) |
| - |
| -There was no old entry to replace. On main, `serve` handed |
| -`mail.NewAWSProvisioner` to `mailBackends` as a function value, which the |
| -census does not count as a call. The boundary has to reach the constructor, so |
| -`serve` now wraps it in a closure, and the closure's call is the site. It is |
| -the same constructor `mailBackends` already called through its parameter, |
| -with one more argument, the boundary ARN. |
| - |
| -Record-write census (9B8): unchanged (the provision package's census tests |
| -pass without an overlay). Claim routes: unchanged. go.mod: unchanged. |
diff --git a/docs/superpowers/notes/census-proposal-sec-sidecar-create-gate.md b/docs/superpowers/notes/census-proposal-sec-sidecar-create-gate.md| new file mode 100644 |
| index 000000000..7fe04527e |
| --- /dev/null |
| +++ b/docs/superpowers/notes/census-proposal-sec-sidecar-create-gate.md |
| @@ -0,0 +1,13 @@ |
| +# Census approval: sec-sidecar-create-gate |
| + |
| +Ruling `census-ruling-sec-sidecar-create-gate`. Every change below keeps the disposition it already has, so it needs an approval, not a decision. |
| + |
| +| unit | change | read sites | what changed | |
| +|---|---|---|---| |
| +| `cmd/console/main.go` `serve` | body | unchanged (3) | serve now parses CARLOS_SIDECAR_ACCOUNTS (sidecarAccounts, a pure env parse with log.Fatalf on a bad or empty-list value) before opening anything and passes it into console.Config.SidecarAccounts. No read was added: the same 3 root read sites. | |
| + |
| +Envelopes re-pinned: `cmd/console`, `internal/console`. |
| + |
| +Seal: `945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a` → `0ca3baafa26f59c0b7d8c6d6a39237d074afb71f885b7fbd50debebab5bf89f2`. |
| + |
| +Overlay: `internal/provision/testdata/plan14-root-reads-sec-sidecar-create-gate.tsv`, appended to `internal/provision/testdata/plan14-root-reads-chain.tsv`. |
diff --git a/go.mod b/go.mod| index 5a0e3cad5..c320d08df 100644 |
| --- a/go.mod |
| +++ b/go.mod |
| @@ -1,6 +1,6 @@ |
| module github.com/carlosframework/platform |
| -go 1.26 |
| +go 1.26.0 |
| require ( |
| github.com/aws/aws-sdk-go-v2 v1.44.0 |
| @@ -21,12 +21,12 @@ require ( |
| github.com/aws/smithy-go v1.28.1 |
| github.com/benbjohnson/litestream v0.5.14 |
| github.com/pion/logging v0.2.4 |
| - github.com/pion/stun/v3 v3.0.1 |
| + github.com/pion/stun/v3 v3.1.5 |
| github.com/pion/turn/v4 v4.1.4 |
| github.com/superfly/ltx v0.5.1 |
| - golang.org/x/crypto v0.53.0 |
| - golang.org/x/net v0.56.0 |
| - golang.org/x/sys v0.46.0 |
| + golang.org/x/crypto v0.57.0 |
| + golang.org/x/net v0.60.0 |
| + golang.org/x/sys v0.48.0 |
| modernc.org/sqlite v1.55.0 |
| ) |
| @@ -60,10 +60,9 @@ require ( |
| github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect |
| github.com/ncruces/go-strftime v1.0.0 // indirect |
| github.com/pierrec/lz4/v4 v4.1.22 // indirect |
| - github.com/pion/dtls/v3 v3.0.7 // indirect |
| + github.com/pion/dtls/v3 v3.1.4 // indirect |
| github.com/pion/randutil v0.1.0 // indirect |
| - github.com/pion/transport/v3 v3.0.8 // indirect |
| - github.com/pion/transport/v4 v4.0.1 // indirect |
| + github.com/pion/transport/v4 v4.0.2 // indirect |
| github.com/prometheus/client_golang v1.17.0 // indirect |
| github.com/prometheus/client_model v0.5.0 // indirect |
| github.com/prometheus/common v0.45.0 // indirect |
| @@ -73,8 +72,9 @@ require ( |
| github.com/tetratelabs/wazero v1.2.1 // indirect |
| github.com/wasilibs/go-re2 v1.3.0 // indirect |
| github.com/wlynxg/anet v0.0.5 // indirect |
| - golang.org/x/sync v0.21.0 // indirect |
| - golang.org/x/text v0.38.0 // indirect |
| + golang.org/x/mod v0.41.0 // indirect |
| + golang.org/x/sync v0.23.0 // indirect |
| + golang.org/x/text v0.42.0 // indirect |
| google.golang.org/protobuf v1.36.10 // indirect |
| modernc.org/libc v1.74.1 // indirect |
| modernc.org/mathutil v1.7.1 // indirect |
| @@ -86,8 +86,7 @@ require ( |
| github.com/aws/aws-sdk-go-v2/service/sns v1.43.0 |
| github.com/klauspost/compress v1.18.0 |
| github.com/oschwald/maxminddb-golang v1.13.1 |
| - golang.org/x/mod v0.37.0 |
| - golang.org/x/tools v0.47.0 |
| + golang.org/x/tools v0.49.0 |
| ) |
| replace amadan.net/carlos/platform.git/launchctx => ./launchctx |
diff --git a/go.sum b/go.sum| index 4558d7955..a7dd6e987 100644 |
| --- a/go.sum |
| +++ b/go.sum |
| @@ -154,18 +154,16 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 |
| github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= |
| github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= |
| github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= |
| -github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q= |
| -github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= |
| +github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY= |
| +github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc= |
| github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= |
| github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= |
| github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= |
| github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= |
| -github.com/pion/stun/v3 v3.0.1 h1:jx1uUq6BdPihF0yF33Jj2mh+C9p0atY94IkdnW174kA= |
| -github.com/pion/stun/v3 v3.0.1/go.mod h1:RHnvlKFg+qHgoKIqtQWMOJF52wsImCAf/Jh5GjX+4Tw= |
| -github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc= |
| -github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= |
| -github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= |
| -github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= |
| +github.com/pion/stun/v3 v3.1.5 h1:Y1FHlhaI6+4UoC5i/zQf4F7JvdZtB24/05oyy/GF1x8= |
| +github.com/pion/stun/v3 v3.1.5/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs= |
| +github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk= |
| +github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= |
| github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= |
| github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= |
| github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= |
| @@ -214,35 +212,25 @@ go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF |
| go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= |
| go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= |
| go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= |
| -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= |
| -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= |
| -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= |
| -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= |
| -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= |
| -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= |
| -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= |
| -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= |
| -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= |
| -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= |
| -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= |
| -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= |
| +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= |
| +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= |
| +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= |
| +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= |
| +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= |
| +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= |
| golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= |
| golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= |
| -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= |
| -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= |
| +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= |
| +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= |
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= |
| -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= |
| -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= |
| -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= |
| -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= |
| -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= |
| -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= |
| -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= |
| -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= |
| -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= |
| -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= |
| -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= |
| +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= |
| +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= |
| +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= |
| +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= |
| +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= |
| +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= |
| +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= |
| +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= |
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
| google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= |
| google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= |
diff --git a/infra/modules/region-host/main.tf b/infra/modules/region-host/main.tf| index b35ecc196..11fca1987 100644 |
| --- a/infra/modules/region-host/main.tf |
| +++ b/infra/modules/region-host/main.tf |
| @@ -490,6 +490,19 @@ resource "aws_instance" "host" { |
| iam_instance_profile = aws_iam_instance_profile.host.name |
| user_data = var.user_data |
| user_data_replace_on_change = false |
| + # IMDSv2 only, one hop. With the block absent EC2 leaves IMDSv1 on, and a |
| + # plain GET from any request-forgery bug in a tenant app reads this |
| + # role's credentials; v2 needs a PUT with a header first, which those |
| + # bugs rarely give an attacker. Hop limit 1 keeps the token from leaving |
| + # the box. Changing these on a running instance is in place, no restart. |
| + # The flagship boxes were switched by hand on 2026-10-23; this keeps a |
| + # replaced box from coming back on v1. The scanner host has done the same |
| + # since it was written. |
| + metadata_options { |
| + http_endpoint = "enabled" |
| + http_tokens = "required" |
| + http_put_response_hop_limit = 1 |
| + } |
| root_block_device { |
| volume_size = var.root_volume_size |
| volume_type = "gp3" |
diff --git a/infra/modules/region-host/tests/instance_metadata.tftest.hcl b/infra/modules/region-host/tests/instance_metadata.tftest.hcl| new file mode 100644 |
| index 000000000..a2c60aa2e |
| --- /dev/null |
| +++ b/infra/modules/region-host/tests/instance_metadata.tftest.hcl |
| @@ -0,0 +1,49 @@ |
| +# Plan-time tests for the host instance's metadata service settings. |
| +# mock_provider means no AWS account and no credentials. |
| +mock_provider "aws" {} |
| + |
| +override_data { |
| + target = data.aws_vpcs.default |
| + values = { ids = ["vpc-0aaaaaaaaaaaaaaaa"] } |
| +} |
| + |
| +override_data { |
| + target = data.aws_caller_identity.current |
| + values = { account_id = "123456789012" } |
| +} |
| + |
| +# The termination queue's ARN feeds an EventBridge target, which the provider |
| +# validates as an ARN; the mock's random string is not one. |
| +override_resource { |
| + target = aws_sqs_queue.terminations |
| + values = { arn = "arn:aws:sqs:eu-west-1:123456789012:carlos-test-host-terminations" } |
| +} |
| + |
| +variables { |
| + name = "carlos-test-ie" |
| + sg_description = "carlos test host" |
| + iam_name = "carlos-test-host" |
| + instance_type = "t4g.small" |
| + root_volume_size = 20 |
| + user_data = "#!/bin/sh\n" |
| + deployment_bucket_arn = "arn:aws:s3:::carlos-test-platform" |
| + data_bucket_arn = "arn:aws:s3:::carlos-test-ie" |
| + ssm_parameter_arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/carlos/*" |
| +} |
| + |
| + |
| +run "imdsv2_only" { |
| + command = plan |
| + assert { |
| + condition = aws_instance.host.metadata_options[0].http_tokens == "required" |
| + error_message = "the host requires IMDSv2 tokens" |
| + } |
| + assert { |
| + condition = aws_instance.host.metadata_options[0].http_put_response_hop_limit == 1 |
| + error_message = "the IMDS token must not leave the box (hop limit 1)" |
| + } |
| + assert { |
| + condition = aws_instance.host.metadata_options[0].http_endpoint == "enabled" |
| + error_message = "the endpoint stays on: the box role is how the host reaches AWS" |
| + } |
| +} |
diff --git a/internal/console/apisidecars.go b/internal/console/apisidecars.go| index 698b7daa4..17c8aa12d 100644 |
| --- a/internal/console/apisidecars.go |
| +++ b/internal/console/apisidecars.go |
| @@ -198,7 +198,8 @@ func (s *Server) handleAPISidecarGet(w http.ResponseWriter, r *http.Request) { |
| // handleAPISidecarCreate is the point of the feature: one create-only write |
| // to the bucket, in the family's refusal order — gate, budget, readiness |
| -// (instanceGate), manifest, name, region, channel, shape, write. |
| +// (instanceGate), manifest, name, region, channel, shape, pause |
| +// (sidecarsgate.go), write. |
| // |
| // Every field policy governs is stamped from the MANIFEST, never from the |
| // request. The body has no pool field to lie in, no host field (the host is |
| @@ -311,6 +312,12 @@ func (s *Server) handleAPISidecarCreate(w http.ResponseWriter, r *http.Request) |
| jsonError(w, http.StatusBadRequest, err.Error()) |
| return |
| } |
| + // The sidecar pause (sidecarsgate.go): after every check above, so a |
| + // stranger still meets the 404 and a bad request its 400, and before |
| + // the cap's listing, the leftover rail and either write arm. |
| + if s.refuseSidecarsPaused(w, acct, app, "create") { |
| + return |
| + } |
| // Plans 3a: the plan caps workers per app, under the account lock. |
| defer s.planLock(acct)() |
| if err := s.checkSidecars(r.Context(), acct, app); err != nil { |
| @@ -586,6 +593,11 @@ func (s *Server) handleAPISidecarEnable(w http.ResponseWriter, r *http.Request) |
| return |
| } |
| } |
| + // The sidecar pause (sidecarsgate.go): after authentication, membership |
| + // and validation, before the manifest read and its write. |
| + if s.refuseSidecarsPaused(w, acct, app, "enable") { |
| + return |
| + } |
| ctx, cancel := context.WithTimeout(r.Context(), instanceStoreTimeout) |
| defer cancel() |
| // Read WITH the etag and write guarded by it below: a read-modify-write |
diff --git a/internal/console/server.go b/internal/console/server.go| index e7c940087..0c186db2f 100644 |
| --- a/internal/console/server.go |
| +++ b/internal/console/server.go |
| @@ -855,6 +855,13 @@ type Config struct { |
| // wherever the two differ by anything but that label; the relay's DNS |
| // names are operator-managed and this console cannot discover them. |
| PlatformDomain string |
| + // SidecarAccounts is the operator allowlist of account sqids that may |
| + // still enable and create native sidecars (CARLOS_SIDECAR_ACCOUNTS, |
| + // normalized by cmd/console: trimmed, lower-cased, each a valid sqid). |
| + // Empty is no gate at all, every deployment's shape before the list |
| + // existed. Set, every other account meets sidecarsPaused on the two |
| + // doors that bring a sidecar into being (sidecarsgate.go). |
| + SidecarAccounts []string |
| } |
| // Server is the console's http.Handler. |
diff --git a/internal/console/sidecarsgate.go b/internal/console/sidecarsgate.go| new file mode 100644 |
| index 000000000..002d4c8e5 |
| --- /dev/null |
| +++ b/internal/console/sidecarsgate.go |
| @@ -0,0 +1,64 @@ |
| +// The sidecar pause: an operator allowlist in front of the two doors that |
| +// bring a native sidecar into being. |
| +// |
| +// Why it exists: a native sidecar runs on the flat tier |
| +// (activator.ExecBackend.StartWorkerStaged) as the box's carlos uid with no |
| +// mount namespace, so on its box it can read and write every nested |
| +// tenant's /data/instances/<iid>. The fix is nested sidecars (amadan |
| +// discussion #370). Until that lands, an operator lists the accounts that |
| +// may still enable or create one (CARLOS_SIDECAR_ACCOUNTS) and every other |
| +// account is refused. |
| +// |
| +// The doors, and why there are only two. A sidecar runs only from a record |
| +// at apps/<sqid>/<app>/sidecars/<name>.json, which the box's sidecar |
| +// reconcile pass (or, under host claim, the root claim path) turns into a |
| +// Kind "sidecar" route, and it takes its argv and ceilings from |
| +// apps/<sqid>/<app>/sidecars.json. The console writes the record in exactly |
| +// one place (handleAPISidecarCreate, both the CreateSidecarRecord and the |
| +// host-claim beginClaimCreate arms) and the manifest in exactly one place |
| +// (handleAPISidecarEnable). Both call refuseSidecarsPaused. Ship and promote |
| +// publish releases and channel pointers, never a sidecar manifest; restore |
| +// clears an app's trash marker and brings back only sidecars that already |
| +// existed; carlos run needs a nested instance. Store mode (`carlos sidecars |
| +// enable` against the bucket) is the operator's own bucket credential and |
| +// is not a console door. |
| +// |
| +// What it leaves alone, on purpose: list, get, wake and delete. Existing |
| +// sidecars keep running, and an account taken off the list must still be |
| +// able to retire its own. |
| +package console |
| + |
| +import ( |
| + "log" |
| + "net/http" |
| + "slices" |
| +) |
| + |
| +// sidecarsPausedMsg is the one sentence a refused account reads. |
| +// copy: approved by Paul 2026-10-24 (copy review). |
| +const sidecarsPausedMsg = "sidecars are paused for this account. existing sidecars keep running, and you can still delete them" |
| + |
| +// sidecarsPausedCode is the refusal's machine-readable code, beside the |
| +// sentence the way plan caps carry "cap". |
| +const sidecarsPausedCode = "sidecars-paused" |
| + |
| +// sidecarsPaused reports whether acct is refused new sidecars: the |
| +// allowlist is set and does not name the account's sqid. |
| +func (s *Server) sidecarsPaused(acct *Account) bool { |
| + return len(s.cfg.SidecarAccounts) > 0 && !slices.Contains(s.cfg.SidecarAccounts, acct.Sqid) |
| +} |
| + |
| +// refuseSidecarsPaused answers 403 sidecars-paused and reports true when |
| +// acct may not enable or create a sidecar. Call it AFTER the door's |
| +// authentication, membership and validation checks, so a non-member still |
| +// gets the byte-identical 404 and the gate never confirms that an account |
| +// exists, and BEFORE any bucket write. Request-path refusals are not |
| +// audited in this console (the plan cap's is not either), so it logs. |
| +func (s *Server) refuseSidecarsPaused(w http.ResponseWriter, acct *Account, app, verb string) bool { |
| + if !s.sidecarsPaused(acct) { |
| + return false |
| + } |
| + log.Printf("console: api sidecars %s %s/%s: refused, account not in CARLOS_SIDECAR_ACCOUNTS", verb, acct.Sqid, app) |
| + writeJSON(w, http.StatusForbidden, map[string]any{"error": sidecarsPausedMsg, "code": sidecarsPausedCode}) |
| + return true |
| +} |
diff --git a/internal/console/sidecarsgate_test.go b/internal/console/sidecarsgate_test.go| new file mode 100644 |
| index 000000000..7f0f90dd0 |
| --- /dev/null |
| +++ b/internal/console/sidecarsgate_test.go |
| @@ -0,0 +1,193 @@ |
| +package console |
| + |
| +// The sidecar pause (sidecarsgate.go), through the real Server, its router |
| +// and the cliRoute auth stack the rest of apisidecars_test.go uses. The |
| +// properties, in the order they would hurt if lost: with the allowlist set, |
| +// an account it does not name cannot enable or create a sidecar by any |
| +// credential that reaches those doors, and nothing is written to the bucket |
| +// when it is refused; a stranger still gets the byte-identical 404, so the |
| +// gate never confirms an account exists; existing sidecars stay listable, |
| +// wakeable and deletable; a named account is unaffected; and with the |
| +// allowlist unset nothing changes at all. |
| + |
| +import ( |
| + "context" |
| + "encoding/json" |
| + "net/http" |
| + "slices" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| + "github.com/carlosframework/platform/internal/svcrole" |
| +) |
| + |
| +// sidecarAppKeys is every bucket key under the fixture app's prefix, sorted: the |
| +// refused calls must leave it exactly as it was. |
| +func sidecarAppKeys(t *testing.T, fx *consoleFixture) []string { |
| + t.Helper() |
| + keys, err := fx.rel.List(context.Background(), release.AppPrefix("bab", "titogo")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + slices.Sort(keys) |
| + return keys |
| +} |
| + |
| +func assertSidecarsPaused(t *testing.T, what string, status int, body string) { |
| + t.Helper() |
| + if status != http.StatusForbidden { |
| + t.Fatalf("%s = %d %s, want 403", what, status, body) |
| + } |
| + var got struct{ Error, Code string } |
| + if err := json.Unmarshal([]byte(body), &got); err != nil { |
| + t.Fatalf("%s body %q: %v", what, body, err) |
| + } |
| + if got.Code != "sidecars-paused" || got.Error != sidecarsPausedMsg { |
| + t.Fatalf("%s body = %+v, want code sidecars-paused and the paused sentence", what, got) |
| + } |
| +} |
| + |
| +// TestSidecarsPausedRefusesEnableAndCreate: the fixture's account (bab) is |
| +// not on the list, so enable and create are refused for the member, the |
| +// legacy app token and an instance-role service principal, and the bucket |
| +// is untouched. |
| +func TestSidecarsPausedRefusesEnableAndCreate(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-side-paused") |
| + fx.srv.cfg.SidecarAccounts = []string{"cdef"} |
| + u := sidecarsURL(fx, "bab", "titogo") |
| + |
| + // Enable with no manifest yet: refused, and no manifest appears. |
| + before := sidecarAppKeys(t, fx) |
| + status, _, out := rawReq(t, http.MethodPut, sidecarEnableURL(fx, "bab", "titogo"), member, `{"channel":"beta"}`) |
| + assertSidecarsPaused(t, "paused enable, no manifest", status, out) |
| + if after := sidecarAppKeys(t, fx); !slices.Equal(before, after) { |
| + t.Fatalf("a refused enable changed the bucket: %v -> %v", before, after) |
| + } |
| + |
| + // An operator-written manifest (store mode, not a console door) is |
| + // there; enable still may not rewrite it. |
| + enableSidecars(t, fx, "titogo", titoSidecarManifest()) |
| + manifest, _, err := fx.rel.Get(context.Background(), provision.SidecarManifestKey("bab", "titogo")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + status, _, out = rawReq(t, http.MethodPut, sidecarEnableURL(fx, "bab", "titogo"), member, `{"channel":"beta","args":["evil"]}`) |
| + assertSidecarsPaused(t, "paused enable over a manifest", status, out) |
| + if now, _, err := fx.rel.Get(context.Background(), provision.SidecarManifestKey("bab", "titogo")); err != nil || string(now) != string(manifest) { |
| + t.Fatalf("a refused enable rewrote the manifest: %s -> %s (%v)", manifest, now, err) |
| + } |
| + |
| + // Create, by every credential the create door admits. |
| + appTok, err := fx.st.NewAppToken(OpsAccountID, "titogo", "alice@keymail.dev", "signup", time.Now()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + svc := serviceToken(t, fx.st, OpsAccountID, "router", "titogo", svcrole.Instance) |
| + before = sidecarAppKeys(t, fx) |
| + for _, c := range []struct{ what, token string }{ |
| + {"member create", member}, |
| + {"app token create", appTok}, |
| + {"instance service principal create", svc}, |
| + } { |
| + status, _, out := rawReq(t, http.MethodPost, u, c.token, `{"name":"bot","region":"ie"}`) |
| + assertSidecarsPaused(t, c.what, status, out) |
| + } |
| + if after := sidecarAppKeys(t, fx); !slices.Equal(before, after) { |
| + t.Fatalf("a refused create changed the bucket: %v -> %v", before, after) |
| + } |
| + if keys, err := fx.rel.List(context.Background(), provision.SidecarsPrefix("bab", "titogo")); err != nil || len(keys) != 0 { |
| + t.Fatalf("refused creates left records %v (%v)", keys, err) |
| + } |
| + |
| + // The gate comes after membership: a stranger and an unknown account |
| + // still get the byte-identical 404, never the 403 that would confirm |
| + // the account exists and is paused. |
| + want := canonicalNotFound(t) |
| + eve := cliToken(t, fx, "eve@keymail.dev", "code-side-paused-eve") |
| + for _, c := range []struct{ what, method, url, token, body string }{ |
| + {"non-member enable", http.MethodPut, sidecarEnableURL(fx, "bab", "titogo"), eve, `{}`}, |
| + {"non-member create", http.MethodPost, u, eve, `{"name":"bot","region":"ie"}`}, |
| + {"unknown account create", http.MethodPost, sidecarsURL(fx, "zzzz", "titogo"), member, `{"name":"bot","region":"ie"}`}, |
| + {"unclaimed app create", http.MethodPost, sidecarsURL(fx, "bab", "ghost-app"), member, `{"name":"bot","region":"ie"}`}, |
| + } { |
| + status, h, out := rawReq(t, c.method, c.url, c.token, c.body) |
| + assertNotFound(t, c.what, want, status, h, out) |
| + } |
| + // And validation still answers first for a member. |
| + if status, _, out := rawReq(t, http.MethodPost, u, member, `{"name":"-nope-","region":"ie"}`); status != http.StatusBadRequest { |
| + t.Fatalf("paused create with a bad name = %d %s, want the 400 first", status, out) |
| + } |
| +} |
| + |
| +// TestSidecarsPausedLeavesExistingSidecarsAlone: a sidecar created before the |
| +// pause is still listed, read, woken and deleted by the paused account, by |
| +// the member and by the instance service principal. |
| +func TestSidecarsPausedLeavesExistingSidecarsAlone(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-side-paused-existing") |
| + enableSidecars(t, fx, "titogo", titoSidecarManifest()) |
| + u := sidecarsURL(fx, "bab", "titogo") |
| + for _, name := range []string{"bot", "bot2"} { |
| + if status, _, out := rawReq(t, http.MethodPost, u, member, `{"name":"`+name+`","region":"ie"}`); status != http.StatusCreated { |
| + t.Fatalf("create %s before the pause = %d %s", name, status, out) |
| + } |
| + } |
| + fx.srv.cfg.SidecarAccounts = []string{"cdef"} |
| + svc := serviceToken(t, fx.st, OpsAccountID, "router", "titogo", svcrole.Instance) |
| + |
| + for _, c := range []struct{ what, method, url string }{ |
| + {"list", http.MethodGet, u}, |
| + {"get", http.MethodGet, u + "/bot"}, |
| + {"wake", http.MethodPost, u + "/bot/wake"}, |
| + } { |
| + if status, _, out := rawReq(t, c.method, c.url, member, ""); status != http.StatusOK { |
| + t.Fatalf("paused %s = %d %s, want 200", c.what, status, out) |
| + } |
| + } |
| + for _, c := range []struct{ name, token string }{{"bot", member}, {"bot2", svc}} { |
| + if status, _, out := rawReq(t, http.MethodDelete, u+"/"+c.name, c.token, ""); status != http.StatusOK { |
| + t.Fatalf("paused delete of %s = %d %s, want 200", c.name, status, out) |
| + } |
| + if _, _, err := fx.rel.Get(context.Background(), provision.SidecarRecordKey("bab", "titogo", c.name)); err == nil { |
| + t.Fatalf("the record for %s survived its delete", c.name) |
| + } |
| + } |
| +} |
| + |
| +// TestSidecarsPauseAllowlistAndUnset: an account the list names, and a |
| +// console with no list at all, enable and create exactly as before. |
| +func TestSidecarsPauseAllowlistAndUnset(t *testing.T) { |
| + for _, c := range []struct { |
| + what string |
| + accts []string |
| + }{ |
| + {"allowlisted", []string{"cdef", "bab"}}, |
| + {"unset", nil}, |
| + } { |
| + t.Run(c.what, func(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-side-allow-"+c.what) |
| + fx.srv.cfg.SidecarAccounts = c.accts |
| + status, _, out := rawReq(t, http.MethodPut, sidecarEnableURL(fx, "bab", "titogo"), member, `{"channel":"stable"}`) |
| + if status != http.StatusOK { |
| + t.Fatalf("enable = %d %s, want 200", status, out) |
| + } |
| + // The pool and regions are operator policy the enable door never |
| + // sets; put them as the operator would so create can place. |
| + enableSidecars(t, fx, "titogo", titoSidecarManifest()) |
| + u := sidecarsURL(fx, "bab", "titogo") |
| + if status, _, out := rawReq(t, http.MethodPost, u, member, `{"name":"bot","region":"ie"}`); status != http.StatusCreated { |
| + t.Fatalf("member create = %d %s, want 201", status, out) |
| + } |
| + svc := serviceToken(t, fx.st, OpsAccountID, "router", "titogo", svcrole.Instance) |
| + if status, _, out := rawReq(t, http.MethodPost, u, svc, `{"name":"bot2","region":"ie"}`); status != http.StatusCreated { |
| + t.Fatalf("service principal create = %d %s, want 201", status, out) |
| + } |
| + for _, name := range []string{"bot", "bot2"} { |
| + if _, err := provision.GetSidecarRecord(context.Background(), fx.rel, "bab", "titogo", name); err != nil { |
| + t.Fatalf("record %s: %v", name, err) |
| + } |
| + } |
| + }) |
| + } |
| +} |
diff --git a/internal/provision/plan9_b8_census_test.go b/internal/provision/plan9_b8_census_test.go| index 525edfb2c..ec0176aee 100644 |
| --- a/internal/provision/plan9_b8_census_test.go |
| +++ b/internal/provision/plan9_b8_census_test.go |
| @@ -366,7 +366,7 @@ func TestPlan9B8RecordWriteCensus(t *testing.T) { |
| if len(got) < 100 { |
| t.Fatalf("census found only %d units under %s; the walk is broken", len(got), root) |
| } |
| - inventory := dbRegionForward3WriteCensusInventory() |
| + inventory := secSidecarCreateGateWriteCensusInventory() |
| problems := append(censusDiff(inventory, got), censusPolicy(inventory)...) |
| if len(problems) > 0 { |
| t.Fatalf("record-write census rejected %d site(s); update the explicit inventory only after reviewing the source:\n %s", len(problems), strings.Join(problems, "\n ")) |
diff --git a/internal/provision/sec_sidecar_create_gate_census_test.go b/internal/provision/sec_sidecar_create_gate_census_test.go| new file mode 100644 |
| index 000000000..c4ecffc32 |
| --- /dev/null |
| +++ b/internal/provision/sec_sidecar_create_gate_census_test.go |
| @@ -0,0 +1,27 @@ |
| +package provision |
| + |
| +// Record-write census entries for the sidecar pause (sec-sidecar-create-gate), |
| +// layered on dbRegionForward3WriteCensusInventory (main's last at 65ca4aca). |
| +// handleAPISidecarCreate gains one early return, refuseSidecarsPaused |
| +// (internal/console/sidecarsgate.go), after the shape check and before the |
| +// plan cap, the leftover rail and both write arms. Its write is unchanged: |
| +// the same CreateSidecarRecord create-only write, or the host-claim create. |
| +// No write site is added; the refusal writes nothing. |
| +func secSidecarCreateGateWriteCensusInventory() []censusEntry { |
| + entries := append([]censusEntry(nil), dbRegionForward3WriteCensusInventory()...) |
| + replacements := []censusEntry{ |
| + {"internal/console/apisidecars.go", "Server.handleAPISidecarCreate", "record-writer", "key:CreateSidecarRecord", "72f4d44028fbc46ed2ed1adca6154f385b62e143f47e597a49b6e28570b2ddab"}, |
| + } |
| + for _, r := range replacements { |
| + found := false |
| + for i, e := range entries { |
| + if e.File == r.File && e.Unit == r.Unit { |
| + entries[i], found = r, true |
| + } |
| + } |
| + if !found { |
| + entries = append(entries, r) |
| + } |
| + } |
| + return entries |
| +} |
diff --git a/internal/provision/testdata/plan14-root-reads-chain.tsv b/internal/provision/testdata/plan14-root-reads-chain.tsv| index 9b02c3c9d..200795c50 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-chain.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-chain.tsv |
| @@ -1,4 +1,7 @@ |
| # Census chain manifest: overlays loaded after rootReadChain's own, in order. |
| # One per line: <overlay file name under testdata>\t<the seal it leaves>. |
| # make census-propose appends here; nothing else edits this file. |
| -plan14-root-reads-sec-mail-user-boundary.tsv 0704f610da881f419f2bc7138a69429c1ceeb1d220b5de24cfda9c0ab91aea02 |
| +plan14-root-reads-sec-host-imdsv2-module.tsv 945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a |
| +plan14-root-reads-sec-deps-dos-bumps.tsv 945934f5978ace14dc5a9bae0ed69dfabf29b994504fc843c03d4a633ecd6c6a |
| +plan14-root-reads-sec-sidecar-create-gate.tsv 0ca3baafa26f59c0b7d8c6d6a39237d074afb71f885b7fbd50debebab5bf89f2 |
| +plan14-root-reads-sec-mail-user-boundary.tsv b64b923281b55c7309e5ecb440cbc0869127e43b6f963849eb30257abb9fba6c |
diff --git a/internal/provision/testdata/plan14-root-reads-sec-deps-dos-bumps.tsv b/internal/provision/testdata/plan14-root-reads-sec-deps-dos-bumps.tsv| new file mode 100644 |
| index 000000000..02c33bd1e |
| --- /dev/null |
| +++ b/internal/provision/testdata/plan14-root-reads-sec-deps-dos-bumps.tsv |
| @@ -0,0 +1,3 @@ |
| +# Census overlay sec-deps-dos-bumps, written by census-propose (#369). census-ruling-sec-deps-dos-bumps, approved by the census owner before the merge; the approval card is docs/superpowers/notes/census-proposal-sec-deps-dos-bumps.md. |
| +# 0 body, 0 sites, 1 envelope: each changed unit re-frozen at its existing disposition with its claim appended. |
| +@coverage . ff7e60e8734238290264ba3b27931d9477811d9a37921a205b1b7244a610bef0 |
diff --git a/internal/provision/testdata/plan14-root-reads-sec-host-imdsv2-module.tsv b/internal/provision/testdata/plan14-root-reads-sec-host-imdsv2-module.tsv| new file mode 100644 |
| index 000000000..c4ac824f6 |
| --- /dev/null |
| +++ b/internal/provision/testdata/plan14-root-reads-sec-host-imdsv2-module.tsv |
| @@ -0,0 +1,5 @@ |
| +# Census overlay sec-host-imdsv2-module, written by census-propose (#369). census-ruling-sec-host-imdsv2-module, approved by the census owner before the merge; the approval card is docs/superpowers/notes/census-proposal-sec-host-imdsv2-module.md. |
| +# 1 body, 0 sites, 1 envelope: each changed unit re-frozen at its existing disposition with its claim appended. |
| +@legacy bf41de79166910448a12a9ba5e5568bc7365193b909d5ebbe942facce8ecb95c infra/modules/region-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's module with one optional aws_iam_role_policy, host_console_mail, count 0 unless console_mail_from is set; it grants the host role ses:SendEmail on the one sending identity, the carlos-bab configuration set and its tenants, conditioned on ses:FromAddress equal to console_mail_from; no read is added and nothing on a box reads the file;rev3=census-ruling-sec-host-imdsv2-module;basis=aws_instance.host gains metadata_options (IMDSv2 required, hop limit 1, endpoint enabled), matching scanner-host; tofu configuration only, run on an operator's machine; no read, file or record write is added; applies in place on a running instance with no restart file:external@0 8ec89e75ca0a0d2ddde74bffe226adb5a5935249fd41590bcce1623c5201ab47 |
| +infra/modules/region-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's module with one optional aws_iam_role_policy, host_console_mail, count 0 unless console_mail_from is set; it grants the host role ses:SendEmail on the one sending identity, the carlos-bab configuration set and its tenants, conditioned on ses:FromAddress equal to console_mail_from; no read is added and nothing on a box reads the file;rev3=census-ruling-sec-host-imdsv2-module;basis=aws_instance.host gains metadata_options (IMDSv2 required, hop limit 1, endpoint enabled), matching scanner-host; tofu configuration only, run on an operator's machine; no read, file or record write is added; applies in place on a running instance with no restart file:external@0 8ec89e75ca0a0d2ddde74bffe226adb5a5935249fd41590bcce1623c5201ab47 |
| +@coverage infra/modules/region-host 5f1b1bbc02603a54e136ff004e2495bef2b0f22054e058b5930a539749b1b558 |
diff --git a/internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv b/internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv| index e9de3c791..10cbe805c 100644 |
| --- a/internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv |
| +++ b/internal/provision/testdata/plan14-root-reads-sec-mail-user-boundary.tsv |
| @@ -1,6 +1,6 @@ |
| # Census overlay sec-mail-user-boundary, written by census-propose (#369). census-ruling-sec-mail-user-boundary, approved by the census owner before the merge; the approval card is docs/superpowers/notes/census-proposal-sec-mail-user-boundary.md. |
| # 1 body, 0 sites, 2 envelope: each changed unit re-frozen at its existing disposition with its claim appended. |
| -@legacy 02f3fd186a4a42fee179d49ea0321ea83c59bbe23e340b9b6b586e1ddab973cd cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-mail-user-boundary;basis=reads CARLOS_MAIL_USER_BOUNDARY (an environment variable, not a file) and log.Fatals when the mail region is set without a valid boundary ARN; passes it to mail.NewAWSProvisioner through a closure; with CARLOS_MAIL_BOUNDARY_SWEEP=1 starts sweepMailBoundaries (IAM calls only, refused under CARLOS_HOST_CLAIM before any backend call); no file or bucket read is added, the same three /data literals moved data-literal@4359,data-literal@5460,data-literal@5697 d0d626616de3c53e94b5853bb6154e0c3efb733fbce99165bfdbd1527bdb9df7 |
| -cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-mail-user-boundary;basis=reads CARLOS_MAIL_USER_BOUNDARY (an environment variable, not a file) and log.Fatals when the mail region is set without a valid boundary ARN; passes it to mail.NewAWSProvisioner through a closure; with CARLOS_MAIL_BOUNDARY_SWEEP=1 starts sweepMailBoundaries (IAM calls only, refused under CARLOS_HOST_CLAIM before any backend call); no file or bucket read is added, the same three /data literals moved data-literal@4359,data-literal@5460,data-literal@5697 d0d626616de3c53e94b5853bb6154e0c3efb733fbce99165bfdbd1527bdb9df7 |
| -@coverage cmd/console 655e1cf47dea7740e4d3598075627b6bf015a7434f14389bc8ba8d27cc1ae9f1 |
| +@legacy 9ad86a176bcc0bd04d4d50ac38ad2d0922023da5ecf35b7fe635dd8049daf21e cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-sidecar-create-gate;basis=serve now parses CARLOS_SIDECAR_ACCOUNTS (sidecarAccounts, a pure env parse with log.Fatalf on a bad or empty-list value) before opening anything and passes it into console.Config.SidecarAccounts. No read was added: the same 3 root read sites.;rev3=census-ruling-sec-mail-user-boundary;basis=reads CARLOS_MAIL_USER_BOUNDARY and passes it to mail.NewAWSProvisioner; runs the one-shot boundary sweep behind CARLOS_MAIL_BOUNDARY_SWEEP=1, refused under CARLOS_HOST_CLAIM data-literal@4601,data-literal@5702,data-literal@5939 d42709a70b4089043a22fc32db306af83d595eb734f1ce050802ba8e85d413ff |
| +cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-sidecar-create-gate;basis=serve now parses CARLOS_SIDECAR_ACCOUNTS (sidecarAccounts, a pure env parse with log.Fatalf on a bad or empty-list value) before opening anything and passes it into console.Config.SidecarAccounts. No read was added: the same 3 root read sites.;rev3=census-ruling-sec-mail-user-boundary;basis=reads CARLOS_MAIL_USER_BOUNDARY and passes it to mail.NewAWSProvisioner; runs the one-shot boundary sweep behind CARLOS_MAIL_BOUNDARY_SWEEP=1, refused under CARLOS_HOST_CLAIM data-literal@4601,data-literal@5702,data-literal@5939 d42709a70b4089043a22fc32db306af83d595eb734f1ce050802ba8e85d413ff |
| +@coverage cmd/console 72c886448386811edaf505aebf56e3455b01b8cf3e88909592c56dc7267b84a5 |
| @coverage internal/mail 82a7f2590ced5c1ad6a72b0e62305b4cc157eeade4e8a3159366a41bc3eaca88 |
diff --git a/internal/provision/testdata/plan14-root-reads-sec-sidecar-create-gate.tsv b/internal/provision/testdata/plan14-root-reads-sec-sidecar-create-gate.tsv| new file mode 100644 |
| index 000000000..ff9e1e70c |
| --- /dev/null |
| +++ b/internal/provision/testdata/plan14-root-reads-sec-sidecar-create-gate.tsv |
| @@ -0,0 +1,6 @@ |
| +# Census overlay sec-sidecar-create-gate, written by census-propose (#369). census-ruling-sec-sidecar-create-gate, approved by the census owner before the merge; the approval card is docs/superpowers/notes/census-proposal-sec-sidecar-create-gate.md. |
| +# 1 body, 0 sites, 2 envelope: each changed unit re-frozen at its existing disposition with its claim appended. |
| +@legacy dd17558eb17031da9bd9c517c68fd97ac5456969222be618e4b24c74128813fd cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-sidecar-create-gate;basis=serve now parses CARLOS_SIDECAR_ACCOUNTS (sidecarAccounts, a pure env parse with log.Fatalf on a bad or empty-list value) before opening anything and passes it into console.Config.SidecarAccounts. No read was added: the same 3 root read sites. data-literal@4319,data-literal@5420,data-literal@5657 1f1aa5c40845b20922a7740ec15254a47ff5856e1ef14252a189e1af56ddd406 |
| +cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=PROPOSED-scan-demand-revival;basis=Console service wires the optional callback signer and starts the claim-guarded queue worker. The same three data path literals move; it does not add a privileged reader.;rev3=PROPOSED-email-signin;identity=the console (cmd/console serve under console.service, user carlos-console);basis=passes CARLOS_CONSOLE_NAME to NewServer as Config.ConsoleName, the name the console's emails use; the same three /data literals at the same offsets; no read added;rev3=PROPOSED-s3-change-index;identity=the console (cmd/console serve under console.service, user carlos-console);basis=arms the record view when CARLOS_CHANGE_INDEX=read (armChangeIndex) for the run and seed sweeps and starts its backstop loop; the same three /data literals at the same offsets; no read added;rev3=census-ruling-db-region-forward-2;identity=the console (cmd/console serve under console.service, user carlos-console);basis=parses CARLOS_DB_FORWARD (dbForward) into Config.DBForward and builds its http.Server with console.NewHTTPServer, the same two timeouts; the same three /data literals at the same offsets; no read added;rev3=census-ruling-sec-sidecar-create-gate;basis=serve now parses CARLOS_SIDECAR_ACCOUNTS (sidecarAccounts, a pure env parse with log.Fatalf on a bad or empty-list value) before opening anything and passes it into console.Config.SidecarAccounts. No read was added: the same 3 root read sites. data-literal@4319,data-literal@5420,data-literal@5657 1f1aa5c40845b20922a7740ec15254a47ff5856e1ef14252a189e1af56ddd406 |
| +@coverage cmd/console 4865ecf0f8de501d62bcfbd10cc9a4244c3e5c5ee53c6c2ee476b5dba7fd36b2 |
| +@coverage internal/console 3ab92802ced1e88b3b841f122fa64b201d576e9dfd5db7662da0028caf7c78ec |