Skip to content
Back to skills

Website Infra

ASecurity

Deploys AWS CloudFormation infrastructure stacks for the website (S3, CloudFront, Route 53, ACM, and optionally contact form API).

  • 391 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 28, 2026
developmentbashawsapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add GRCEngClub/claude-grc-engineering --skill website-infra --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Website Infra?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Website Infra
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/grcengclub-website-infra/badge)](https://www.skillsdirectory.com/skills/grcengclub-website-infra)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: website-infra
description: Deploys AWS CloudFormation infrastructure stacks for the website (S3, CloudFront, Route 53, ACM, and optionally contact form API).
allowed-tools: Bash, Read, Write, Edit, Glob
---

# Website Infrastructure Skill

You are running the `/grc-portfolio:infra` skill. Your job is to deploy the AWS infrastructure needed to host the website using CloudFormation.

## Step 1: Locate and Validate Config

Find `site-config.json`:
- Check `$ARGUMENTS` for a project directory path
- Check the current working directory
- Ask the user if not found

Read it and validate that `status.preflightComplete === true`. If not, tell the user to run `/grc-portfolio:preflight` first.

## Step 2: Choose Template

Based on `features.customDomain`:
- **With custom domain**: Use `$TOOLKIT_DIR/cloudformation/website-infrastructure.yaml`
- **Without custom domain**: Use `$TOOLKIT_DIR/cloudformation/website-infrastructure-no-domain.yaml`

## Step 3: Validate Template

Run the validation script:
```bash
$TOOLKIT_DIR/scripts/validate-stack.sh <template-path> <aws.profile>
```

If validation fails, report the error and stop.

## Step 4: Deploy Main Stack

Use `aws cloudformation deploy` — it is idempotent (creates the stack on
first run, applies a change-set on subsequent runs) and waits for completion
inline, so the user doesn't need a separate `wait` step.

**With custom domain:**
```bash
aws cloudformation deploy \
  --stack-name <aws.stackName> \
  --template-file <template-path> \
  --parameter-overrides \
    ProjectName=<projectName> \
    DomainName=<aws.domain> \
    HostedZoneId=<aws.hostedZoneId> \
    CertificateArn=<aws.certArn> \
  --profile <aws.profile> \
  --region us-east-1 \
  --no-fail-on-empty-changeset
```

**Without custom domain:**
```bash
aws cloudformation deploy \
  --stack-name <aws.stackName> \
  --template-file <template-path> \
  --parameter-overrides ProjectName=<projectName> \
  --profile <aws.profile> \
  --region us-east-1 \
  --no-fail-on-empty-changeset
```

Note: Check the actual template parameters first by reading the template file, as parameter names may differ from the examples above. Use the actual parameter names from the template.

The deploy command can take 5-15 minutes (especially with CloudFront
distribution creation). Tell the user it's in progress and approximately
how long it might take.

If the deploy fails, retrieve the failure reason:
```bash
aws cloudformation describe-stack-events \
  --stack-name <aws.stackName> \
  --profile <aws.profile> \
  --region us-east-1 \
  --query "StackEvents[?ResourceStatus=='CREATE_FAILED' || ResourceStatus=='UPDATE_FAILED'].[LogicalResourceId,ResourceStatusReason]" \
  --output table
```

## Step 6: Retrieve Stack Outputs

```bash
aws cloudformation describe-stacks \
  --stack-name <aws.stackName> \
  --profile <aws.profile> \
  --region us-east-1 \
  --query "Stacks[0].Outputs"
```

Extract and save to `site-config.json`:
- `aws.bucketName` -- the S3 bucket name
- `aws.distributionId` -- the CloudFront distribution ID
- `aws.cloudFrontUrl` -- the CloudFront domain name (e.g., d123abc.cloudfront.net)

## Step 7: Deploy Contact Form API (if enabled)

If `features.contactForm` is true:

1. Ask the user for two SES-verified email addresses (no defaults — the
   stack will refuse to deploy without them):
   - `SESFromEmail`: verified sender identity
   - `SESToEmail`: recipient inbox for submissions

   If either is not yet verified in SES, stop and instruct the user to verify
   them in the AWS Console (`SES → Verified identities → Create identity`)
   before continuing. Save both to `aws.sesFromEmail` and `aws.sesToEmail`
   in `site-config.json`.

2. Deploy the contact form stack:
```bash
aws cloudformation deploy \
  --template-file $TOOLKIT_DIR/cloudformation/contact-form-api.yaml \
  --stack-name <projectName>-contact-form-api \
  --parameter-overrides "SESFromEmail=<aws.sesFromEmail>" "SESToEmail=<aws.sesToEmail>" \
  --capabilities CAPABILITY_IAM \
  --profile <aws.profile> \
  --region us-east-1 \
  --no-fail-on-empty-changeset
```

3. Retrieve the API endpoint:
```bash
aws cloudformation describe-stacks \
  --stack-name <projectName>-contact-form-api \
  --profile <aws.profile> \
  --region us-east-1 \
  --query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \
  --output text
```

4. Save to `aws.contactApiEndpoint` in config.

5. Package and deploy the Lambda function code (reference `$TOOLKIT_DIR/scripts/deploy-contact-api.sh` for the pattern -- create temp dir, copy handler, install @aws-sdk/client-ses, zip, update function code). The function name is `<projectName>-contact-form-api-handler` (derived from the stack name in the template).

## Step 8: Domain Instructions (if custom domain)

If using a custom domain, tell the user:
- The nameservers they need to set at their domain registrar
- How to verify DNS propagation: `dig NS <domain>`
- That DNS propagation can take up to 48 hours (usually much faster)

Get the nameservers:
```bash
aws route53 get-hosted-zone --id <aws.hostedZoneId> --profile <aws.profile> --query "DelegationSet.NameServers"
```

## Step 9: Update Config

Update `site-config.json`:
- Set `aws.bucketName`, `aws.distributionId`, `aws.cloudFrontUrl`
- Set `aws.contactApiEndpoint` (if contact form)
- Set `status.infraDeployed = true`

## Step 10: Summary

Tell the user:
- Infrastructure deployed successfully
- CloudFront URL where the site will be accessible
- Contact form API endpoint (if applicable)
- DNS instructions (if custom domain)
- Suggest running `/grc-portfolio:deploy` next to push the built site to AWS

## Variables

- `$TOOLKIT_DIR` = read from `site-config.json` `toolkitDir` field
- `$ARGUMENTS` = arguments passed after `/infra` (expected: project directory path)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.