Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Last active December 14, 2024 22:19
Show Gist options
  • Save brennanMKE/3e0d6d180ebb6840dd6580bb0bce1ba3 to your computer and use it in GitHub Desktop.
Save brennanMKE/3e0d6d180ebb6840dd6580bb0bce1ba3 to your computer and use it in GitHub Desktop.
Bluesky Accounts Server on AWS

Bluesky Accounts Server on AWS

ATProto Well-Known Routes

On Bluesky and other ATProto-based platforms, users can associate their accounts with their own domain names instead of relying on service-owned subdomains. To do this, you need to prove that you control the domain by publishing a special file at a specific, “well-known” location or by adding a DNS record. Once verified, your account’s handle can become something like @username.example.com instead of @username.bsky.social.

For organizations that want to create multiple user accounts, using a separate hostname for each user makes management easier. By setting up a wildcard DNS record (e.g., *.example.com), you can quickly create new hostnames for any user without having to run separate websites or add individual DNS entries. Each of these hostnames can return a unique DID (Decentralized Identifier) to ATProto services through a simple, automated process.

In the following example, DID values are stored as simple JSON files. A small CGI script reads the hostname requested, finds the matching JSON file, and serves it at the well-known route. This JSON file not only provides the DID needed for verification on Bluesky or other ATProto services, but also includes additional information like a user’s name and a redirect URL. When someone visits the root of the user’s hostname, they’re automatically redirected to a profile page—such as a LinkedIn profile or an organizational directory entry—providing a unified way to confirm the user’s identity and affiliation.

By setting up a system like this, organizations can maintain complete control over their identities on social media platforms, guide visitors back to their own web properties, and ensure that their domain names remain the authoritative source for verifying user identities.


1. Creating an EC2 Server

  1. Launch an EC2 Instance:

    • Go to the AWS Management Console.
    • Navigate to EC2 and click Launch Instances.
    • Select an Amazon Linux 2023 AMI.
    • Choose an ARM-based instance type, such as t4g.micro, for cost efficiency.
  2. Configure Instance Details:

    • Choose default settings for network.
    • Ensure SSH access is enabled.
  3. Add Storage:

    • Use the default storage configuration.
  4. Launch and Connect:

    • Download the key pair (.pem file).

    • Connect using SSH:

      ssh -i /path/to/key.pem ec2-user@<your-instance-public-ip>
  5. Update Packages:

    sudo yum update -y
  6. Install Apache:

    sudo yum install -y httpd
    sudo systemctl start httpd
    sudo systemctl enable httpd

2. Configuring Route 53 for Wildcard DNS

  1. Open Route 53:

    • Navigate to Route 53 in the AWS Console.
    • Select your hosted zone for the domain (e.g., example.com).
  2. Add a Wildcard DNS Record:

    • Create an A Record:
      • Name: *.example.com
      • Type: A
      • Value: Public IP of your EC2 instance.
    • Add an additional A Record for example.com pointing to the same IP.
  3. Test the DNS Record:

    dig user1.example.com

3. Configuring the Web Server

  1. Create the VirtualHost Configuration: Edit the Apache configuration file:

    sudo vim /etc/httpd/conf.d/example.com.conf

    Add the following:

    <VirtualHost *:80>
        ServerName example.com
        ServerAlias *.example.com
    
        DocumentRoot "/var/www/html"
    
        ScriptAlias /cgi-bin/ /var/www/cgi-bin/
        Alias /.well-known/atproto-did /var/www/cgi-bin/atproto-did.cgi
    
        <Directory "/var/www/cgi-bin">
            AllowOverride None
            Options +ExecCGI
            Require all granted
        </Directory>
    </VirtualHost>
  2. Restart Apache:

    sudo systemctl restart httpd

4. Configuring the CGI Script

  1. Create the CGI Script:

    sudo vim /var/www/cgi-bin/atproto-did.cgi

    Paste the script:

     #!/bin/bash
    
     # Base directory for profile JSON files
     PROFILE_DIR="/var/www/profiles"
    
     # Get the hostname (subdomain part)
     HOSTNAME=$(echo "$HTTP_HOST" | cut -d. -f1)
    
     # Validate that HOSTNAME only contains valid characters: letters, numbers, hyphens, and underscores
     if [[ ! "$HOSTNAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
         echo "Content-Type: text/plain"
         echo
         echo "Invalid hostname."
         exit 1
     fi
    
     # Path to the profile JSON file
     PROFILE_FILE="${PROFILE_DIR}/${HOSTNAME}.json"
    
     # Serve /.well-known/atproto-did
     if [[ "$REQUEST_URI" == "/.well-known/atproto-did" ]]; then
         if [[ -f "$PROFILE_FILE" ]]; then
             # Extract the "did" value using jq
             DID_PLC=$(jq -r ".\"did\"" "$PROFILE_FILE")
             
             # Ensure both values exist before responding
             if [[ -n "$DID_PLC" ]]; then
                 echo "Content-Type: text/plain"
                 echo
                 echo "$DID_PLC"
             else
                 # Missing required fields
                 echo "Content-Type: text/plain"
                 echo
                 echo "Missing required DID fields in the profile."
             fi
         else
             # Profile file not found
             echo "Content-Type: text/plain"
             echo
             echo "Profile not found."
         fi
         exit 0
     fi
    
     # Redirect to the profile URL for root access
     if [[ -f "$PROFILE_FILE" ]]; then
         REDIRECT_URL=$(jq -r ".url" "$PROFILE_FILE")
         if [[ -n "$REDIRECT_URL" ]]; then
             echo "Content-Type: text/html"
             echo "Status: 302 Found"
             echo "Location: $REDIRECT_URL"
             echo
             echo "<html><head><title>Redirecting...</title></head><body>Redirecting to <a href=\"$REDIRECT_URL\">$REDIRECT_URL</a></body></html>"
         else
             echo "Content-Type: text/plain"
             echo
             echo "Redirect URL not found."
         fi
     else
         echo "Content-Type: text/plain"
         echo
         echo "Profile not found."
     fi
  2. Set Permissions:

    sudo chmod +x /var/www/cgi-bin/atproto-did.cgi
  3. Create the Profiles Directory:

    sudo mkdir -p /var/www/profiles
    sudo vim /var/www/profiles/user1.json

    Example JSON:

    {
        "name": "User One",
        "url": "https://www.linkedin.com/in/user1",
        "did-plc": "did:plc:z72i7hdynmk6r22h6tvur",
    }

5. Creating an IAM User for Certbot

  1. Create IAM User:

    • Go to IAMUsersAdd Users.

    • Assign programmatic access and attach the following policy:

      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "route53:ListHostedZones",
                      "route53:GetChange",
                      "route53:ChangeResourceRecordSets"
                  ],
                  "Resource": "*"
              }
          ]
      }
  2. Store AWS Credentials:

    mkdir -p ~/.aws
    vim ~/.aws/credentials

    Add:

    [default]
    aws_access_key_id = YOUR_ACCESS_KEY
    aws_secret_access_key = YOUR_SECRET_KEY
  3. Copy Credentials for root:

    sudo mkdir -p /root/.aws
    sudo cp ~/.aws/credentials /root/.aws/credentials
    sudo chmod 600 /root/.aws/credentials

6. Running Certbot

  1. Install Certbot and Route 53 Plugin:

    sudo yum install -y python3-certbot-dns-route53
  2. Run Certbot:

    sudo certbot -a dns-route53 -i apache -d "*.example.com" -d "example.com"
  3. Verify SSL: Test HTTPS for both https://example.com and https://sub.example.com.


7. Setting Up Certificate Renewal

  1. Create a Systemd Timer:

    sudo vim /etc/systemd/system/certbot-renew.service

    Add:

    [Unit]
    Description=Certbot Renewal
    
    [Service]
    Type=oneshot
    ExecStart=/usr/bin/certbot renew --quiet
  2. Create the Timer:

    sudo vim /etc/systemd/system/certbot-renew.timer

    Add:

    [Unit]
    Description=Run Certbot Renewal Twice Daily
    
    [Timer]
    OnCalendar=*-*-* 00,12:00:00
    Persistent=true
    
    [Install]
    WantedBy=timers.target
  3. Enable the Timer:

    sudo systemctl enable certbot-renew.timer
    sudo systemctl start certbot-renew.timer
  4. Test the Timer:

    sudo certbot renew --dry-run

8. Redirect HTTP to HTTPS

Update the VirtualHost under /etc/httpd/conf.d which should now have rewrite rules. Add the following rule to redirect HTTP requests to HTTPS.

RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
#!/bin/bash
# Base directory for profile JSON files
PROFILE_DIR="/var/www/profiles"
# Get the hostname (subdomain part)
HOSTNAME=$(echo "$HTTP_HOST" | cut -d. -f1)
# Validate that HOSTNAME only contains valid characters: letters, numbers, hyphens, and underscores
if [[ ! "$HOSTNAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Content-Type: text/plain"
echo
echo "Invalid hostname."
exit 1
fi
# Path to the profile JSON file
PROFILE_FILE="${PROFILE_DIR}/${HOSTNAME}.json"
# Serve /.well-known/atproto-did
if [[ "$REQUEST_URI" == "/.well-known/atproto-did" ]]; then
if [[ -f "$PROFILE_FILE" ]]; then
# Extract the did-plc and did-web values using jq
DID_PLC=$(jq -r ".\"did-plc\"" "$PROFILE_FILE")
# Ensure both values exist before responding
if [[ -n "$DID_PLC" ]]; then
echo "Content-Type: text/plain"
echo
echo "$DID_PLC"
else
# Missing required fields
echo "Content-Type: text/plain"
echo
echo "Missing required DID fields in the profile."
fi
else
# Profile file not found
echo "Content-Type: text/plain"
echo
echo "Profile not found."
fi
exit 0
fi
# Redirect to the profile URL for root access
if [[ -f "$PROFILE_FILE" ]]; then
REDIRECT_URL=$(jq -r ".url" "$PROFILE_FILE")
if [[ -n "$REDIRECT_URL" ]]; then
echo "Content-Type: text/html"
echo "Status: 302 Found"
echo "Location: $REDIRECT_URL"
echo
echo "<html><head><title>Redirecting...</title></head><body>Redirecting to <a href=\"$REDIRECT_URL\">$REDIRECT_URL</a></body></html>"
else
echo "Content-Type: text/plain"
echo
echo "Redirect URL not found."
fi
else
echo "Content-Type: text/plain"
echo
echo "Profile not found."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment