mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: improve rbac and add benchmark tooling (#18584)
## Description This PR improves the RBAC package by refactoring the policy, enhancing documentation, and adding utility scripts. ## Changes * Refactored `policy.rego` for clarity and readability * Updated README with OPA section * Added `benchmark_authz.sh` script for authz performance testing and comparison * Added `gen_input.go` to generate input for `opa eval` testing
This commit is contained in:
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run rbac authz benchmark tests on the current Git branch or compare benchmark results
|
||||
# between two branches using `benchstat`.
|
||||
#
|
||||
# The script supports:
|
||||
# 1) Running benchmarks and saving output to a file.
|
||||
# 2) Checking out two branches, running benchmarks on each, and saving the `benchstat`
|
||||
# comparison results to a file.
|
||||
# Benchmark results are saved with filenames based on the branch name.
|
||||
#
|
||||
# Usage:
|
||||
# benchmark_authz.sh --single # Run benchmarks on current branch
|
||||
# benchmark_authz.sh --compare <branchA> <branchB> # Compare benchmarks between two branches
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Go benchmark parameters
|
||||
GOMAXPROCS=16
|
||||
TIMEOUT=30m
|
||||
BENCHTIME=5s
|
||||
COUNT=5
|
||||
|
||||
# Script configuration
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUTPUT_DIR="${SCRIPT_DIR}/benchmark_outputs"
|
||||
|
||||
# List of benchmark tests
|
||||
BENCHMARKS=(
|
||||
BenchmarkRBACAuthorize
|
||||
BenchmarkRBACAuthorizeGroups
|
||||
BenchmarkRBACFilter
|
||||
)
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
function run_benchmarks() {
|
||||
local branch=$1
|
||||
# Replace '/' with '-' for branch names with format user/branchName
|
||||
local filename_branch=${branch//\//-}
|
||||
local output_file_prefix="$OUTPUT_DIR/${filename_branch}"
|
||||
|
||||
echo "Checking out $branch..."
|
||||
git checkout "$branch"
|
||||
|
||||
# Move into the rbac directory to run the benchmark tests
|
||||
pushd ../../coderd/rbac/ >/dev/null
|
||||
|
||||
for bench in "${BENCHMARKS[@]}"; do
|
||||
local output_file="${output_file_prefix}_${bench}.txt"
|
||||
echo "Running benchmark $bench on $branch..."
|
||||
GOMAXPROCS=$GOMAXPROCS go test -timeout $TIMEOUT -bench="^${bench}$" -run=^$ -benchtime=$BENCHTIME -count=$COUNT | tee "$output_file"
|
||||
done
|
||||
|
||||
# Return to original directory
|
||||
popd >/dev/null
|
||||
}
|
||||
|
||||
if [[ $# -eq 0 || "${1:-}" == "--single" ]]; then
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
run_benchmarks "$current_branch"
|
||||
elif [[ "${1:-}" == "--compare" ]]; then
|
||||
base_branch=$2
|
||||
test_branch=$3
|
||||
|
||||
# Run all benchmarks on both branches
|
||||
run_benchmarks "$base_branch"
|
||||
run_benchmarks "$test_branch"
|
||||
|
||||
# Compare results benchmark by benchmark
|
||||
for bench in "${BENCHMARKS[@]}"; do
|
||||
# Replace / with - for branch names with format user/branchName
|
||||
filename_base_branch=${base_branch//\//-}
|
||||
filename_test_branch=${test_branch//\//-}
|
||||
|
||||
echo -e "\nGenerating benchmark diff for $bench using benchstat..."
|
||||
benchstat "$OUTPUT_DIR/${filename_base_branch}_${bench}.txt" "$OUTPUT_DIR/${filename_test_branch}_${bench}.txt" | tee "$OUTPUT_DIR/${bench}_diff.txt"
|
||||
done
|
||||
else
|
||||
echo "Usage:"
|
||||
echo " $0 --single # run benchmarks on current branch"
|
||||
echo " $0 --compare branchA branchB # compare benchmarks between two branches"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,100 @@
|
||||
// This program generates an input.json file containing action, object, and subject fields
|
||||
// to be used as input for `opa eval`, e.g.:
|
||||
// > opa eval --format=pretty "data.authz.allow" -d policy.rego -i input.json
|
||||
// This helps verify that the policy returns the expected authorization decision.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
)
|
||||
|
||||
type SubjectJSON struct {
|
||||
ID string `json:"id"`
|
||||
Roles []rbac.Role `json:"roles"`
|
||||
Groups []string `json:"groups"`
|
||||
Scope rbac.Scope `json:"scope"`
|
||||
}
|
||||
type OutputData struct {
|
||||
Action policy.Action `json:"action"`
|
||||
Object rbac.Object `json:"object"`
|
||||
Subject *SubjectJSON `json:"subject"`
|
||||
}
|
||||
|
||||
func newSubjectJSON(s rbac.Subject) (*SubjectJSON, error) {
|
||||
roles, err := s.Roles.Expand()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to expand subject roles: %w", err)
|
||||
}
|
||||
scopes, err := s.Scope.Expand()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to expand subject scopes: %w", err)
|
||||
}
|
||||
return &SubjectJSON{
|
||||
ID: s.ID,
|
||||
Roles: roles,
|
||||
Groups: s.Groups,
|
||||
Scope: scopes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Support optional CLI flags to customize the input:
|
||||
// --action=[one of the supported actions]
|
||||
// --subject=[one of the built-in roles]
|
||||
// --object=[one of the supported resources]
|
||||
func main() {
|
||||
// Template Admin user
|
||||
subject := rbac.Subject{
|
||||
FriendlyName: "Test Name",
|
||||
Email: "test@coder.com",
|
||||
Type: "user",
|
||||
ID: uuid.New().String(),
|
||||
Roles: rbac.RoleIdentifiers{
|
||||
rbac.RoleTemplateAdmin(),
|
||||
},
|
||||
Scope: rbac.ScopeAll,
|
||||
}
|
||||
|
||||
subjectJSON, err := newSubjectJSON(subject)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to convert to subject to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Delete action
|
||||
action := policy.ActionDelete
|
||||
|
||||
// Prebuilt Workspace object
|
||||
object := rbac.Object{
|
||||
ID: uuid.New().String(),
|
||||
Owner: "c42fdf75-3097-471c-8c33-fb52454d81c0",
|
||||
OrgID: "663f8241-23e0-41c4-a621-cec3a347318e",
|
||||
Type: "prebuilt_workspace",
|
||||
}
|
||||
|
||||
// Output file path
|
||||
outputPath := "input.json"
|
||||
|
||||
output := OutputData{
|
||||
Action: action,
|
||||
Object: object,
|
||||
Subject: subjectJSON,
|
||||
}
|
||||
|
||||
outputBytes, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal output to json: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outputPath, outputBytes, 0o600); err != nil {
|
||||
log.Fatalf("Failed to generate input file: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Input JSON written to", outputPath)
|
||||
}
|
||||
Reference in New Issue
Block a user