{"id":"b31b7c53-1ff7-4810-83ba-0d2bb242ffde","slug":"cloud-bootstrap","kind":"skill","name":"cloud-bootstrap","summary":"A skill for Claude Code on the Web that bootstraps cloud provider credentials (GCP, AWS, Azure) and stores them encrypted in your repo.","owner":"ipeirotis","repoUrl":"https://github.com/ipeirotis/cloud-bootstrap","license":"MIT","licenseTier":"permissive","trust":"community","stars":7,"match":null,"fewStars":false,"format":"skill","createdAt":"2026-03-16T17:18:13.000Z","addedAt":"2026-10-21T22:46:00.926Z","logoUrl":"https://avatars.githubusercontent.com/u/152561?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","developer-tools"],"tags":["ai-agents","aws","azure","claude-code","cloud","gcp"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-25T16:30:17.588Z","sample":false,"url":"https://conduits.dev/tools/cloud-bootstrap","language":"Shell","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# cloud-bootstrap\n\n[![Version](https://img.shields.io/badge/version-1.4.0-blue.svg)](CHANGELOG.md)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n[![Claude Skill](https://img.shields.io/badge/Claude_Code-Skill-blueviolet.svg)](https://code.claude.com/docs/en/skills)\n[![Providers](https://img.shields.io/badge/Providers-GCP_|_AWS_|_Azure-orange.svg)](#supported-providers)\n\n**Encrypted cloud credentials that survive Claude Code sessions — set up once, authenticate forever.**\n\nA skill for [Claude Code on the Web](https://claude.com/product/claude-code) that bootstraps cloud provider credentials (GCP, AWS, Azure) and stores them encrypted in your repo. One install, and every future session auto-authenticates in seconds.\n\n---\n\n## Features at a Glance\n\n- **Zero-touch session auth** — SessionStart hook decrypts and authenticates automatically, every session\n- **Multi-provider** — GCP, AWS, and Azure in the same repo, each with independent credentials\n- **Multi-team** — Each team member has their own encrypted key and passphrase, no secrets shared\n- **AES-256-CBC encryption** — Credentials are encrypted with OpenSSL; only the passphrase stays outside the repo\n- **Automatic key rotation warnings** — Flags credentials older than 180 days\n- **Minimal IAM footprint** — Proposes least-privilege roles, never escalates on its own\n- **7 built-in workflows** — Setup, onboarding, authentication, rotation, escalation, multi-provider, and uninstall\n- **One-line install** — `curl | bash` and you're done\n\n---\n\n## How It Compares\n\n| Approach | Persistent across sessions? | Works in Claude Code Web? | No external service needed? | Per-user encryption? |\n|---|---|---|---|---|\n| **cloud-bootstrap** | Yes | Yes | Yes | Yes |\n| GCP Secret Manager | Yes | Needs API access first | No | No |\n| AWS Secrets Manager | Yes | Needs API access first | No | No |\n| HashiCorp Vault | Yes | No (requires server) | No | Configurable |\n| `.env` files | No (wiped each session) | Partially | Yes | No |\n| Paste token each session | No | Yes | Yes | N/A |\n\ncloud-bootstrap solves the chicken-and-egg problem: you need credentials to access a secret manager, but you need the secret manager to store credentials.\n\n---\n\n## The Problem\n\nClaude Code on the Web has no persistent storage across sessions except the repo itself. If your project needs cloud access, you need a way to store credentials that:\n\n- Survives across sessions\n- Travels with the repo\n- Doesn't expose secrets in git history\n- Lets the agent authenticate without human intervention each session\n\n## How It Works\n\n**First user (one-time setup):**\n\n1. You tell the agent which cloud provider and project to use\n2. The agent proposes minimum roles; you approve\n3. You generate a short-lived token locally and paste it in\n4. The agent creates a service account, encrypts the key with `openssl`, and commits the encrypted file\n5. In future sessions, the agent decrypts and authenticates automatically\n\n**Additional team members:**\n\n1. A teammate opens the repo in Claude Code on the Web\n2. The agent sees the existing cloud config but no credentials for this user\n3. The teammate generates a short-lived token and pastes it in\n4. The agent creates a new key for the same service account, encrypted with the teammate's own passphrase\n5. No passwords are shared between team members\n\nEach person's encrypted credentials are stored as `.cloud-credentials.<git-email>.enc`. The service account, roles, and project config are shared.\n\nThe only secret not in the repo is the encryption passphrase, which you set as an environment variable in Claude Code on the Web. You can use a provider-specific variable (`GCP_CREDENTIALS_KEY`, `AWS_CREDENTIALS_KEY`, `AZURE_CREDENTIALS_KEY`) or a universal one (`CLOUD_CREDENTIALS_KEY`). The skill checks the provider-specific variable first and falls back to the universal one.\n\nThis means if you work with multiple providers across different repos, you can use distinct passphrases per provider. If you only use one provider, just set `CLOUD_CREDENTIALS_KEY` and forget about it.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    Your Repository                          │\n│                                                             │\n│  .cloud-config.json          ← provider, project, roles    │\n│  .cloud-credentials.*.enc   ← AES-256-CBC encrypted keys   │\n│  .claude/hooks/cloud-auth.sh ← SessionStart auto-auth      │\n│  .claude/skills/cloud-bootstrap/SKILL.md ← this skill      │\n│                                                             │\n├─────────────────────────────────────────────────────────────┤\n│                    Each Session                              │\n│                                                             │\n│  1. SessionStart hook fires                                 │\n│  2. Decrypts credentials using env var passphrase           │\n│  3. Activates cloud CLI (gcloud/aws/az)                     │\n│  4. Deletes plaintext key immediately                       │\n│  5. Cloud access ready — no human intervention              │\n└─────────────────────────────────────────────────────────────┘\n```\n\n## Quick Start\n\n```bash\n# 1. Install the skill (from your repo root)\ncurl -sSL https://raw.githubusercontent.com/ipeirotis/cloud-bootstrap/main/install.sh | bash\n\n# 2. Set your encryption passphrase in Claude Code on the Web\n#    (Settings → Environment Variables)\n#    CLOUD_CREDENTIALS_KEY = <your-secret-passphrase>\n\n# 3. Tell Claude:\n#    \"Set up GCP credentials for project my-project-id\"\n\n# That's it. Every future session authenticates automatically.\n```\n\n## Install\n\n**One-liner** (from your repo root):\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/ipeirotis/cloud-bootstrap/main/install.sh | bash\n```\n\n**Manual install** (if you prefer to see each step):\n\n```bash\n# From the repo root\n# Every file from one commit: main can move between downloads\n# Resolved with git, not GitHub's REST API, whose unauthenticated quota is\n# shared by everyone behind the same IP address\nSHA=$(git ls-remote https://github.com/ipeirotis/cloud-bootstrap.git refs/heads/main | cut -f1)\nprintf '%s' \"$SHA\" | grep -qxE '[0-9a-f]{40}' || { echo \"ERROR: could not resolve the current release commit.\"; exit 1; }\nBASE=https://raw.githubusercontent.com/ipeirotis/cloud-bootstrap/$SHA\nDEST=.claude/skills/cloud-bootstrap\n\n# MANIFEST lists every distributed file (including scripts/discard-credential.sh,\n# which the workflows call when a new credential has to be revoked)\n# Download everything to a temp dir first; install only if every file arrived,\n# so a failed download never leaves a mix of old and new files\nSTAGE=$(mktemp -d) && [ -n \"$STAGE\" ] && [ -d \"$STAGE\" ] \\\n  || { echo \"ERROR: could not create a staging directory; nothing changed.\"; exit 1; }\ntrap 'rm -rf \"$STAGE\"' EXIT\n# To a file first: a download cut off midway must fail here, not leave a\n# shorter list that installs only part of the skill\ncurl -fsSL \"$BASE/MANIFEST\" -o \"$STAGE/.manifest\" \\\n  || { echo \"ERROR: could not download MANIFEST; nothing changed.\"; exit 1; }\nFILES=$(grep -v -e '^[[:space:]]*#' -e '^[[:space:]]*$' \"$STAGE/.manifest\"); rm -f \"$STAGE/.manifest\"\n[ -n \"$FILES\" ] || { echo \"ERROR: MANIFEST is empty; nothing changed.\"; exit 1; }\n# Refuse any path outside the skill before creating a directory or file\nfor FILE in $FILES; do\n  case \"$FILE\" in /*|*..*) echo \"ERROR: MANIFEST lists an unsafe path ($FILE); nothing changed.\"; exit 1 ;; esac\ndone\nfor FILE in $FILES; do\n  mkdir -p \"$STAGE/$(dirname \"$FILE\")\"\n  curl -fsSL \"$BASE/$FILE\" -o \"$STAGE/$FILE\" || { echo \"ERROR: could not download $FILE; nothing changed.\"; rm -rf \"$STAGE\"; exit 1; }\ndone\n# As install.sh does: every file SKILL.md or a workflow names, and the revocation helper,\n# must have arrived before the old copy is replaced\nfor REQ in VERSION SKILL.md scripts/discard-credential.sh \\\n           $(cat \"$STAGE/SKILL.md\" \"$STAGE\"/workflows/*.md 2>/dev/null \\\n             | grep -oE '(workflows|references|scripts)/[A-Za-z0-9_-]+\\.(md|sh)' | sort -u); do\n  [ -s \"$STAGE/$REQ\" ] || { echo \"ERROR: the release lacks $REQ; nothing changed.\"; exit 1; }\ndone\n# Record what was installed, as install.sh does, so update.sh can later remove\n# files a newer release drops\nprintf '%s\\n' $FILES > \"$STAGE/.installed-files\"\n# Swap the complete staged copy in with renames (as update.sh does): a failure\n# leaves the existing installation untouched, never a mix of two releases\nmkdir -p \"$(dirname \"$DEST\")\"\nNEW=$(mktemp -d \"$(dirname \"$DEST\")/.cloud-bootstrap.new.XXXXXX\") \\\n  || { echo \"ERROR: could not create a directory next to $DEST; nothing changed.\"; exit 1; }\nOLD_DIR=\"$(dirname \"$DEST\")/.cloud-bootstrap.old.$$\"\ntrap 'rm -rf \"$STAGE\" \"$NEW\"; if [ -d \"$OLD_DIR\" ] && [ ! -e \"$DEST\" ]; then mv \"$OLD_DIR\" \"$DEST\"; fi' EXIT\ncp -R \"$STAGE\"/. \"$NEW\"/ && chmod 755 \"$NEW\" \\\n  || { echo \"ERROR: could not prepare the new copy; nothing changed.\"; exit 1; }\nif [ -e \"$DEST\" ]; then mv \"$DEST\" \"$OLD_DIR\" || { echo \"ERROR: could not move the old copy aside; nothing changed.\"; exit 1; }; fi\nmv \"$NEW\" \"$DEST\" || { echo \"ERROR: could not move the new copy into place; the old one is restored.\"; exit 1; }\nrm -rf \"$OLD_DIR\"\n\ngit add \"$DEST\"\ngit commit -m \"Add cloud-bootstrap skill\" -- \"$DEST\"   # only the skill, not anything else already staged\n```\n\n**Or** just tell Claude Code on the Web:\n\n> \"Clone the cloud-bootstrap skill from https://github.com/ipeirotis/cloud-bootstrap into `.claude/skills/cloud-bootstrap/` in this repo and commit it.\"\n\n## Updating\n\nCheck which version you have and whether a newer one is available:\n\n```bash\ncurl -sSL https://raw.githubusercontent.com/ipeirotis/cloud-bootstrap/main/update.sh | bash\n```\n\nThis will:\n1. Show your installed version and the latest version\n2. Display the changelog entries you'd be getting\n3. Ask for confirmation on your terminal before updating (where no terminal is available, run `... | bash -s -- --yes`)\n\nYou can also check your installed version at any time: `cat .claude/skills/cloud-bootstrap/VERSION`.\n\n## Prerequisites\n\n- Encryption passphrase set as an environment variable in Claude Code on the Web: either `GCP_CREDENTIALS_KEY` / `AWS_CREDENTIALS_KEY` / `AZURE_CREDENTIALS_KEY`, or the universal `CLOUD_CREDENTIALS_KEY`\n- A cloud account with permission to create service accounts and assign roles\n- `openssl` available in the environment (standard on CCoW)\n\n## Supported Providers\n\n| Provider | Bootstrap Command | What Gets Created | Team Limit |\n|----------|------------------|-------------------|------------|\n| GCP | `gcloud auth print-access-token` | Service account + JSON key per user | ~10 (keys per SA) |\n| AWS | `aws sts get-session-token` | IAM group + IAM user per team member | Bounded by the account's IAM user quota |\n| Azure | `az account get-access-token` | Service principal + client secret per user | Bounded: secrets share the app manifest's credential limit, and rotated or departed members' secrets count until removed |\n\n## Files Created in Your Repo\n\n| File | Purpose | Committed? |\n|------|---------|------------|\n| `.cloud-credentials.<email>.enc` | Encrypted key, one per team member | Yes |\n| `.cloud-config.json` | Provider, project ID, roles (shared) | Yes |\n| `.claude/hooks/cloud-auth.sh` | SessionStart hook: installs CLI + authenticates | Yes |\n| `.claude/settings.json` | Hook configuration | Yes |\n| `CLAUDE.md` (Cloud Credentials section) | Human/agent-readable auth docs | Yes |\n| `credentials.json` | **Never** (plaintext key) | `.gitignore`d |\n\n**Multi-provider repos:** When using multiple providers, credential files are named `.cloud-credentials.<provider>.<email>.enc` and `.cloud-config.json` uses a `providers` array format. See SKILL.md for details.\n\n## Security Model\n\n- Each team member's credentials are encrypted with their own passphrase (never shared)\n- Credentials are encrypted with AES-256-CBC (via `openssl`)\n- Plaintext keys exist only momentarily during setup and authentication, and are deleted immediately\n- The agent cannot escalate its own permissions; it must ask you\n- Bootstrap tokens expire in ~1 hour\n- Encryption passphrases never enter the repo\n- Revoking a team member's access: delete their `.cloud-credentials.<email>.enc` file and remove their key/user from the cloud provider\n\n## Troubleshooting\n\n| Symptom | Likely Cause | Fix |\n|---------|-------------|-----|\n| \"No credentials key found\" | Passphrase env var not set | Set `CLOUD_CREDENTIALS_KEY` in Claude Code Web settings |\n| Decryption fails silently | Wrong passphrase or corrupted `.enc` file | Re-run setup with the correct passphrase |\n| 403 after authentication | Service account missing a role | Ask your admin to grant the needed role (skill will suggest which one) |\n| CLI not found after hook runs | Install failed or PATH issue | Re-open session; hook retries install on next start |\n| Credential age warning | Key older than 180 days | Run credential rotation workflow |\n\n## License\n\nMIT\n"}