Policy
The ezoidc server must be configured with a policy written in Rego v1.
The policy must define cases for the allow.read
and allow.internal
functions to implement the access control rules for the variables. The body of each function case should verify the issuer, subject, and claims of the token
to ensure the client is authorized to access the variable.
Functions
allow.read(name)
Determines if the variable name
can be read.
# only allow "api_key"allow.read("api_key") if { issuer = "github" subject = "repo:owner/repo:ref:refs/heads/main"}
# allow all variables starting with "github/"allow.read(name) if { startswith(name, "github/") issuer = "github" subject = "repo:owner/repo:ref:refs/heads/main"}
# allow all variables to the configured issuersallow.read(_)
allow.internal(name)
Determines if the variable name
is internal. Internal variables are loaded during the policy evaluation for variable definitions, but are not intended to be returned in the response to the client.
# allow all variables starting with "internal/"allow.internal(name) if { startswith(name, "internal/") # validate issuer, subject, claims}
Policy Input
issuer
The identifier used in the configuration to identify the issuer that the token was issued by.
variables: github_secret: "value"issuers: github: issuer: https://token.actions.githubusercontent.com
allow.read("github_secret") if { issuer = "github" subject = "repo:owner/repo:ref:refs/heads/main" claims.iss = "https://token.actions.githubusercontent.com"}
subject
The sub
claim of the token. This value is the same as claims.sub
.
allow.read("github_secret") if { issuer = "github" subject = "repo:owner/repo:ref:refs/heads/main" claims.sub = "repo:owner/repo:ref:refs/heads/main"}
claims
An object with the validated token claims.
allow.read("github_secret") if { issuer = "github" claims.repository = "owner/repo" claims.ref = "refs/heads/main"}
params
An object with user-provided parameters.
allow.read("read_access") if { issuer = "github" claims.repository_owner = "owner" params.scope = "read"}
allow.read("write_access") if { issuer = "github" subject = "repo:owner/repo:ref:refs/heads/main" params.scope = "write"}
Variable Definitions
After the policy is evaluated to determine which variables are allowed, the policy is evaluated once more using the allowed variables value and can be used to derive new variables.
allow.read("defined_variable_name")define.defined_variable_name.value = "variable value"define.defined_variable_name.export = "ENV_VAR_NAME"
There are some considerations when using variable definitions:
- Variable definitions are not allowed by default. Their name must be allowed using
allow.read
in order to be returned to the client. - The
value
of a variable definition must be a string. - The
name
of variable definitions cannot make use of variables and must be a literal string. For instance:- This is OK:
define["defined_variable_name"].value = "foo"
- This will error:
define[variable_name].value = "foo" if variable_name == "defined_variable_name"
- This is OK:
read(name)
This function reads the value of the variable name
. This function is only intended to be used in variable definitions in the policy after the variable was allowed using allow.read
or allow.internal
.
allow.read("variable_name")allow.internal("internal_variable")define.variable_name.value = read("internal_variable")
If the variable was not allowed or could not be loaded, the rule evaluation will halt and a warning message will be logged.
Utilities
fetch
This function is a wrapper of the built-in http.send
function.
The request
object expected is the same, but with some defaults:
- The default HTTP method is
GET
. - The
User-Agent
header defaults to the ezoidc server version. - If the request is successful, the requested URL is logged at the
debug
level. - If the request fails, the requested URL is logged at the
warn
level.
fetch({ "method": "POST", "url": "https://example.com/api", "body": { "json": true }, "headers": { "Authorization": "...", },}) = { "status": "200 OK", "status_code": 200, "body": {...}, "raw_body": "...", "headers": {...},}
cloudflare_r2_temporary_credentials
Use the Cloudflare R2 API to generate temporary credentials. If successful, the API response object is returned. See Cloudflare’s documentation for more information.
cloudflare_r2_temporary_credentials({ # Cloudflare API token "token": read("cloudflare_api_token"), # Cloudflare account ID "account_id": "023e105f4ecef8ad9ca31a8372d0c353", # Parent access key ID (required) "parent_access_key_id": read("access_key_id"), # Cloudflare R2 bucket name (required) "bucket": "bucket_name", # Duration of the temporary credentials in seconds (required) "ttl_seconds": 900, # Permission for the temporary credentials (required) "permission": "object-read-only", # List of objects to generate temporary credentials for (optional) "objects": ["object_name"], # List of prefixes to generate temporary credentials for (optional) "prefixes": ["example-prefix/"],}) = { "errors": [ { "code": 1000, "message": "string" } ], "messages": ["string"], "result": { "accessKeyId": "example-access-key-id", "secretAccessKey": "example-secret-key", "sessionToken": "example-session-token" }, "success": true}
Example
variables: internal/cloudflare_account_id: <f028a08a135be5aa81d577eb0fece3a6> internal/cloudflare_bucket: <bucket-name> internal/cloudflare_token: value: { env: CLOUDFLARE_TOKEN } internal/parent_access_key_id: value: { env: CLOUDFLARE_ACCESS_KEY_ID }policy: | allow.internal(name) if startswith(name, "internal/") allow.read(name) if { startswith(name, "r2/") # validate issuer, subject, claims }
# Export R2 credentials as variables for the AWS CLI define["r2/aws_access_key_id"] = r2_variable("AWS_ACCESS_KEY_ID") define["r2/aws_secret_access_key"] = r2_variable("AWS_SECRET_ACCESS_KEY") define["r2/aws_session_token"] = r2_variable("AWS_SESSION_TOKEN") define["r2/aws_endpoint_url"] = { "value": sprintf("https://%s.r2.cloudflarestorage.com", [read("internal/cloudflare_account_id")]), "export": "AWS_ENDPOINT_URL", }
r2_variable(export) := {"value": value, "export": export} if { response := cloudflare_r2_temporary_credentials({ "token": read("internal/cloudflare_token"), "account_id": read("internal/cloudflare_account_id"), "bucket": read("internal/cloudflare_bucket"), "parentAccessKeyId": read("internal/parent_access_key_id"), "permission": "object-read-only", "ttlSeconds": 900, }) map := { "AWS_ACCESS_KEY_ID": "accessKeyId", "AWS_SECRET_ACCESS_KEY": "secretAccessKey", "AWS_SESSION_TOKEN": "sessionToken", } value := response.result[map[export]] }
providers.aws.sign_req
This OPA built-in function is used to sign a request object using AWS Signature Version 4. See OPA’s documentation for more information.
request = { "method": "GET", "url": "https://bucket.s3.amazonaws.com/file.txt"}aws_config = { "aws_access_key": read("aws_access_key"), "aws_secret_access_key": read("aws_secret_access_key"), "aws_service": "s3", "aws_region": "us-east-1",}signed_request = providers.aws.sign_req(request, aws_config, time.now_ns())
github_app_jwt
This function generates a JWT for a GitHub App. See GitHub’s documentation for more information.
github_app_jwt({ # GitHub App private key in PEM format "private_key": read("github_app_private_key") # GitHub App ID "app_id": 123,}) = "jwt"
github_app_installation_token
This function generates a GitHub App installation token. See GitHub’s documentation for more information.
github_app_installation_token({ # GitHub App private key in PEM format "private_key": read("github_app_private_key") # GitHub App ID "app_id": 123, # GitHub App installation ID "installation_id": 456, # Body of the request to downscope the token (optional) "body": { "permissions": { "contents": "read" }, "repositories": ["org/repo"], },}) = "ghs_rwo6..."
Example
variables: internal/github_app_private_key: value: { env: GITHUB_APP_PRIVATE_KEY } internal/github_app_id: <app-id> internal/github_installation_id: <installation-id>policy: | allow.internal(name) if startswith(name, "internal/") allow.read("github_token") if { # validate issuer, subject, claims }
define.github_token.export = "GH_TOKEN" define.github_token.value = github_app_installation_token({ "private_key": read("internal/github_app_private_key"), "app_id": read("internal/github_app_id"), "installation_id": read("internal/github_installation_id"), "body": { "permissions": { "contents": "read" }, }, })
totp_verify
Validate a time-based one-time password (TOTP) code using HMAC-SHA1.
totp_verify({ # Shared secret (required) "secret": read("totp_secret"), # Code to validate (required) "code": "123456", # Time period in seconds "period": 30, # Number of time periods to allow for skew "skew": 0, # Current time in nanoseconds "time": time.now_ns(),}) = true
Example
allow.internal("totp_secret")allow.read("privileged_token") if { # validate issuer, subject, claims}
define.privileged_token.value = "token" if { totp_verify({ "secret": read("totp_secret"), "code": params.code })}