Skip to content

Instantly share code, notes, and snippets.

@johnandersen777
Created June 12, 2026 19:07
Show Gist options
  • Select an option

  • Save johnandersen777/15f8eb181529024ca485c39be6bfaacc to your computer and use it in GitHub Desktop.

Select an option

Save johnandersen777/15f8eb181529024ca485c39be6bfaacc to your computer and use it in GitHub Desktop.

droplet-oidc-poc demo

$ mkdir rbac
cd rbac
git init
Initialized empty Git repository in /home/johnandersen777/.tmp/tmp.pVjmEYIeok/rbac/.git/
$ vim setup.sh
$ bash -xe !$
bash -xe setup.sh
+ mkdir policies
+ mkdir roles
+ mkdir droplet-roles
+ tee policies/ex-database-and-spaces-keys-access.hcl
path "/v1/oidc/issue" {
  capabilities = ["create"]
  allowed_parameters = {
    "aud" = "api://DigitalOcean?actx={actx}"
    "sub" = "actx:{actx}:role:database-and-spaces-keys-access"
    "ttl" = 300
  }
}
+ tee droplet-roles/ex-database-and-spaces-keys-access.hcl
role "ex-database-and-spaces-keys-access" {
  aud      = "api://DigitalOcean?actx={actx}"
  sub      = "actx:{actx}:role:ex-database-and-spaces-keys-access"
  policies = ["ex-database-and-spaces-keys-access"]
}
+ tee policies/database-credential-read.hcl
path "/v2/databases/d4c0539c-1590-4cf7-9f21-361f79cf8361" {
  # Enable read of a single database by UUID.
  capabilities = ["read"]
}

path "/v2/databases" {
  # Enable read of databases tagged with given tag name.
  # ? is used for query parameters.
  capabilities = ["read"]
  allowed_parameters = {
    "?" = {
      "tag_name" = "my-tag"
    }
  }
}
+ tee policies/spaces-keys.hcl
path "/v2/spaces/keys" {
  # Enable creation of Spaces keys to access a named bucket.
  # Allowed parameters not in the top level ? key are treated
  # as POST body keys whose values must match hcl defined values.
  capabilities = ["create"]
  allowed_parameters = {
    "name" = "bucket-111-read-token-*"
    "grants" = [
      {
        "bucket" = "111"
        "permission" = "read"
      }
    ]
  }
}

path "/v2/spaces/keys/*" {
  # Enable deletion of Spaces keys.
  capabilities = ["delete"]
}
+ tee roles/database-and-spaces-keys-access.hcl
role "database-and-spaces-keys-access" {
  aud      = "api://DigitalOcean?actx={actx}"
  sub      = "actx:{actx}:role:database-and-spaces-keys-access"
  policies = ["database-credential-read", "spaces-keys"]
}
+ export THIS_ENDPOINT=https://droplet-oidc-poc-2-dwyva.ondigitalocean.app
+ THIS_ENDPOINT=https://droplet-oidc-poc-2-dwyva.ondigitalocean.app
+ mkdir -p /home/johnandersen777/.local/scripts/
+ tee /home/johnandersen777/.local/scripts/git-credential-rbac-digitalocean.sh
#!/usr/bin/env bash

TOKEN=$(doctl auth token)

while IFS='=' read -r key value; do
  if [[ -n "$key" && -n "$value" ]]; then
    if [[ "$key" == "protocol" || "$key" == "host" ]]; then
      echo "$key=$value"
    fi
  fi
done

echo "username=token"
# https://git-scm.com/docs/git-credential documents how this style of
# script works, stdin / stdout is used for communication to / from git
# and the bash process executing this script. Since we always use the
# doctl local PAT for authentication to this PoC deployment, we don't need
# to add custom logic around if this host or if this protocol, we always
# use the token for the deployed FQDN (git config --global
# credential."${THIS_ENDPOINT}".helper)
echo "password=${TOKEN}"
+ chmod 700 /home/johnandersen777/.local/scripts/git-credential-rbac-digitalocean.sh
+ git init
Reinitialized existing Git repository in /home/johnandersen777/.tmp/tmp.pVjmEYIeok/rbac/.git/
+ git config --global credential.https://droplet-oidc-poc-2-dwyva.ondigitalocean.app.helper '!/home/johnandersen777/.local/scripts/git-credential-rbac-digitalocean.sh'
+ git branch -M main
+ git add .
+ git commit -sm 'feat: configure access from droplet'
[main (root-commit) 71c5e4c] feat: configure access from droplet
 6 files changed, 165 insertions(+)
 create mode 100644 droplet-roles/ex-database-and-spaces-keys-access.hcl
 create mode 100644 policies/database-credential-read.hcl
 create mode 100644 policies/ex-database-and-spaces-keys-access.hcl
 create mode 100644 policies/spaces-keys.hcl
 create mode 100644 roles/database-and-spaces-keys-access.hcl
 create mode 100644 setup.sh
+ git remote add deploy https://droplet-oidc-poc-2-dwyva.ondigitalocean.app
+ git push -u deploy main
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 16 threads
Compressing objects: 100% (11/11), done.
Writing objects: 100% (11/11), 2.71 KiB | 2.71 MiB/s, done.
Total 11 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To https://droplet-oidc-poc-2-dwyva.ondigitalocean.app
 * [new branch]      main -> main
branch 'main' set up to track 'deploy/main'.
+ git fetch --all
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (3/3), 1017 bytes | 1017.00 KiB/s, done.
From https://droplet-oidc-poc-2-dwyva.ondigitalocean.app
 * [new branch]      schema     -> deploy/schema
+ git show deploy/schema:rbac.json
+ jq
{
  "custom_claims_roles_index": {
    "job_workflow_ref": {}
  },
  "policies": {
    "database-credential-read": {
      "meta": {
        "policy": "database-credential-read"
      },
      "schemas": {
        "/v2/databases": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "properties": {
            "body": {
              "additionalProperties": false,
              "properties": {
                "?": {
                  "const": {
                    "tag_name": "my-tag"
                  },
                  "type": "object"
                }
              },
              "required": [
                "?"
              ],
              "type": "object"
            },
            "capability": {
              "enum": [
                "read"
              ]
            }
          },
          "required": [
            "capability"
          ],
          "type": "object"
        },
        "/v2/databases/d4c0539c-1590-4cf7-9f21-361f79cf8361": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "properties": {
            "capability": {
              "enum": [
                "read"
              ]
            }
          },
          "required": [
            "capability"
          ],
          "type": "object"
        }
      }
    },
    "ex-database-and-spaces-keys-access": {
      "meta": {
        "policy": "ex-database-and-spaces-keys-access"
      },
      "schemas": {
        "/v1/oidc/issue": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "properties": {
            "body": {
              "additionalProperties": false,
              "properties": {
                "aud": {
                  "const": "api://DigitalOcean?actx=4959ec0923473bf22bddd7bec2caf58a294ee007",
                  "type": "string"
                },
                "sub": {
                  "const": "actx:4959ec0923473bf22bddd7bec2caf58a294ee007:role:database-and-spaces-keys-access",
                  "type": "string"
                },
                "ttl": {
                  "const": 300,
                  "type": "number"
                }
              },
              "required": [
                "aud",
                "sub",
                "ttl"
              ],
              "type": "object"
            },
            "capability": {
              "enum": [
                "create"
              ]
            }
          },
          "required": [
            "capability"
          ],
          "type": "object"
        }
      }
    },
    "spaces-keys": {
      "meta": {
        "policy": "spaces-keys"
      },
      "schemas": {
        "/v2/spaces/keys": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "properties": {
            "body": {
              "additionalProperties": false,
              "properties": {
                "grants": {
                  "items": {
                    "anyOf": [
                      {
                        "additionalProperties": false,
                        "properties": {
                          "bucket": {
                            "const": "111"
                          },
                          "permission": {
                            "const": "read"
                          }
                        },
                        "required": [
                          "bucket",
                          "permission"
                        ],
                        "type": "object"
                      }
                    ]
                  },
                  "type": "array"
                },
                "name": {
                  "pattern": "bucket-111-read-token-.*",
                  "type": "string"
                }
              },
              "required": [
                "grants",
                "name"
              ],
              "type": "object"
            },
            "capability": {
              "enum": [
                "create"
              ]
            }
          },
          "required": [
            "capability"
          ],
          "type": "object"
        },
        "/v2/spaces/keys/*": {
          "$schema": "http://json-schema.org/draft-07/schema#",
          "properties": {
            "capability": {
              "enum": [
                "delete"
              ]
            }
          },
          "required": [
            "capability"
          ],
          "type": "object"
        }
      }
    }
  },
  "roles": {
    "database-and-spaces-keys-access": {
      "definition": {
        "aud": "api://DigitalOcean?actx=4959ec0923473bf22bddd7bec2caf58a294ee007",
        "policies": [
          "database-credential-read",
          "spaces-keys"
        ],
        "sub": "actx:4959ec0923473bf22bddd7bec2caf58a294ee007:role:database-and-spaces-keys-access"
      },
      "role_name": "database-and-spaces-keys-access"
    },
    "ex-database-and-spaces-keys-access": {
      "definition": {
        "aud": "api://DigitalOcean?actx=4959ec0923473bf22bddd7bec2caf58a294ee007",
        "policies": [
          "ex-database-and-spaces-keys-access"
        ],
        "sub": "actx:4959ec0923473bf22bddd7bec2caf58a294ee007:role:ex-database-and-spaces-keys-access"
      },
      "role_name": "ex-database-and-spaces-keys-access"
    }
  }
}
$ doctl \
    --api-url "https://droplet-oidc-poc-2-dwyva.ondigitalocean.app" \
    compute droplet create \
    --region sfo2 \
    --size s-1vcpu-1gb \
    --image ubuntu-24-04-x64 \
    --tag-names "oidc-sub:role:ex-database-and-spaces-keys-access" \
    test-droplet-0001
ID           Name                 Public IPv4    Private IPv4    Public IPv6    Memory    VCPUs    Disk    Region    Image                     VPC UUID    Status    Tags                                                Features         Volumes
577242657    test-droplet-0001                                                  1024      1        25      sfo2      Ubuntu 24.04 (LTS) x64                new       oidc-sub:role:ex-database-and-spaces-keys-access    droplet_agent
$ doctl compute d list
ID           Name                                    Public IPv4        Private IPv4    Public IPv6                     Memory    VCPUs    Disk    Region    Image                     VPC UUID                                Status    Tags                                                Features                                            Volumes
577242657    test-droplet-0001                       157.245.234.20     10.138.0.3                                      1024      1        25      sfo2      Ubuntu 24.04 (LTS) x64    5ea03263-94e8-489e-85e6-c64128ef9a71    active    oidc-sub:role:ex-database-and-spaces-keys-access    droplet_agent,private_networking
$ ssh root@157.245.234.20
The authenticity of host '157.245.234.20 (157.245.234.20)' can't be established.
ED25519 key fingerprint is: SHA256:EYEq0qJubqeraFQU0SF9lMsFYg0AGsMZyt+rJyXBlHs
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '157.245.234.20' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-71-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Fri Jun 12 19:01:12 UTC 2026

  System load:  0.75              Processes:             109
  Usage of /:   8.2% of 23.17GB   Users logged in:       0
  Memory usage: 23%               IPv4 address for eth0: 157.245.234.20
  Swap usage:   0%                IPv4 address for eth0: 10.46.0.6

Expanded Security Maintenance for Applications is not enabled.

198 updates can be applied immediately.
150 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@test-droplet-0001:~# URL=$(cat /root/secrets/digitalocean.com/serviceaccount/base_url)
TEAM_UUID=$(cat /root/secrets/digitalocean.com/serviceaccount/team_uuid)
ID_TOKEN=$(cat /root/secrets/digitalocean.com/serviceaccount/token)
root@test-droplet-0001:~# SUBJECT="actx:${TEAM_UUID}:role:database-and-spaces-keys-access"

TOKEN=$(curl -sf \
  -H "Authorization: Bearer ${ID_TOKEN}" \
  -d@<(jq -n -c \
    --arg aud "api://DigitalOcean?actx=${TEAM_UUID}" \
    --arg sub "${SUBJECT}" \
    --arg ttl 300 \
    '{aud: $aud, sub: $sub, ttl: ($ttl | fromjson)}') \
  "${URL}/v1/oidc/issue" \
  | jq -r .token)
root@test-droplet-0001:~# DATABASE_UUID=d4c0539c-1590-4cf7-9f21-361f79cf8361
root@test-droplet-0001:~#
curl -sf \
  -H "Authorization: Bearer ${TOKEN}" \
  "${URL}/v2/databases/${DATABASE_UUID}" \
  | jq -r .database.connection.uri

https://doadmin:REDACTEDcNQDREDACTEDuYFQ@compute-contracts-0001-do-user-6349623-0.j.db.ondigitalocean.com:25060
mkdir policies
mkdir roles
mkdir droplet-roles
tee policies/ex-database-and-spaces-keys-access.hcl <<'EOF'
path "/v1/oidc/issue" {
capabilities = ["create"]
allowed_parameters = {
"aud" = "api://DigitalOcean?actx={actx}"
"sub" = "actx:{actx}:role:database-and-spaces-keys-access"
"ttl" = 300
}
}
EOF
tee droplet-roles/ex-database-and-spaces-keys-access.hcl <<'EOF'
role "ex-database-and-spaces-keys-access" {
aud = "api://DigitalOcean?actx={actx}"
sub = "actx:{actx}:role:ex-database-and-spaces-keys-access"
policies = ["ex-database-and-spaces-keys-access"]
}
EOF
tee policies/database-credential-read.hcl <<'EOF'
path "/v2/databases/d4c0539c-1590-4cf7-9f21-361f79cf8361" {
# Enable read of a single database by UUID.
capabilities = ["read"]
}
path "/v2/databases" {
# Enable read of databases tagged with given tag name.
# ? is used for query parameters.
capabilities = ["read"]
allowed_parameters = {
"?" = {
"tag_name" = "my-tag"
}
}
}
EOF
tee policies/spaces-keys.hcl <<'EOF'
path "/v2/spaces/keys" {
# Enable creation of Spaces keys to access a named bucket.
# Allowed parameters not in the top level ? key are treated
# as POST body keys whose values must match hcl defined values.
capabilities = ["create"]
allowed_parameters = {
"name" = "bucket-111-read-token-*"
"grants" = [
{
"bucket" = "111"
"permission" = "read"
}
]
}
}
path "/v2/spaces/keys/*" {
# Enable deletion of Spaces keys.
capabilities = ["delete"]
}
EOF
tee roles/database-and-spaces-keys-access.hcl <<'EOF'
role "database-and-spaces-keys-access" {
aud = "api://DigitalOcean?actx={actx}"
sub = "actx:{actx}:role:database-and-spaces-keys-access"
policies = ["database-credential-read", "spaces-keys"]
}
EOF
# Define the FQDN of your deployed API proxy
export THIS_ENDPOINT="https://droplet-oidc-poc-2-dwyva.ondigitalocean.app"
mkdir -p "${HOME}/.local/scripts/"
tee "${HOME}/.local/scripts/git-credential-rbac-digitalocean.sh" <<'EOF'
#!/usr/bin/env bash
TOKEN=$(doctl auth token)
while IFS='=' read -r key value; do
if [[ -n "$key" && -n "$value" ]]; then
if [[ "$key" == "protocol" || "$key" == "host" ]]; then
echo "$key=$value"
fi
fi
done
echo "username=token"
# https://git-scm.com/docs/git-credential documents how this style of
# script works, stdin / stdout is used for communication to / from git
# and the bash process executing this script. Since we always use the
# doctl local PAT for authentication to this PoC deployment, we don't need
# to add custom logic around if this host or if this protocol, we always
# use the token for the deployed FQDN (git config --global
# credential."${THIS_ENDPOINT}".helper)
echo "password=${TOKEN}"
EOF
chmod 700 "${HOME}/.local/scripts/git-credential-rbac-digitalocean.sh"
git init
git config --global credential."${THIS_ENDPOINT}".helper \
'!'"${HOME}/.local/scripts/git-credential-rbac-digitalocean.sh"
git branch -M main
git add .
git commit -sm "feat: configure access from droplet"
git remote add deploy "${THIS_ENDPOINT}"
git push -u deploy main
# View deployed config
git fetch --all && git show deploy/schema:rbac.json | jq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment