¿Por qué utilizar una función personalizada?
Las condiciones en SPDL pueden invocar una función configurada cuando los operadores o funciones integradas no permiten expresar la regla requerida. Speedle invoca su endpoint REST mediante POST {"params":[...]} y espera recibir {"result":...} o {"error":"..."}. El orden/tipo de los parámetros y el tipo del resultado deben coincidir con la condición. Los fallos en las funciones constituyen errores de evaluación; las aplicaciones deben fallar de forma restrictiva (fail closed).
Iniciar el ejemplo local
Este ejemplo acepta valores numéricos entre 100 y 50000. Los tipos de parámetros incorrectos o una lista de parámetros vacía devuelven un error HTTP. Utiliza texto en plano únicamente en una dirección IP de loopback; utilice TLS para otras interfaces.
Inícielo en una terminal independiente:
go run ./website/tests/examples/custom-functionpackage main
import (
"encoding/json"
"flag"
"log"
"net"
"net/http"
"time"
)
type request struct {
Params []interface{} `json:"params"`
}
type response struct {
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
func isValid(w http.ResponseWriter, r *http.Request) {
send := func(status int, value response) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(value)
}
if r.Method != http.MethodPost {
w.Header().Set("Allow", "POST")
send(405, response{Error: "POST required"})
return
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20))
decoder.DisallowUnknownFields()
var input request
if err := decoder.Decode(&input); err != nil || len(input.Params) == 0 {
send(400, response{Error: "params must contain at least one number"})
return
}
valid := true
for _, param := range input.Params {
value, ok := param.(float64)
if !ok {
send(400, response{Error: "all params must be numbers"})
return
}
if value < 100 || value > 50000 {
valid = false
}
}
send(200, response{Result: valid})
}
func main() {
listen := flag.String("listen", "127.0.0.1:23456", "Function listen address")
cert := flag.String("cert", "", "TLS server certificate")
key := flag.String("key", "", "TLS server key")
flag.Parse()
if (*cert == "") != (*key == "") {
log.Fatal("provide both --cert and --key")
}
mux := http.NewServeMux()
mux.HandleFunc("/func/isValid", isValid)
server := &http.Server{Addr: *listen, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second}
if *cert != "" {
log.Fatal(server.ListenAndServeTLS(*cert, *key))
}
host, _, err := net.SplitHostPort(*listen)
if err != nil || !net.ParseIP(host).IsLoopback() {
log.Fatal("plaintext examples require a loopback IP; provide TLS certificates for other addresses")
}
log.Fatal(server.ListenAndServe())
}Antes de ejecutar los ejemplos
Compile ./bin/spctl, ./bin/speedle-pms y ./bin/speedle-ads mediante el inicio rápido. Utilice Go 1.26.10 o Go 1.27.3 (o un parche más reciente de la misma rama de lanzamiento), Bash, curl y Python 3. Ejecute cada terminal desde la raíz del repositorio. Utilice un archivo de políticas nuevo para esta página; reinícielo únicamente tras detener ambos servicios.
Inicialice un archivo de políticas vacío:
printf '{}\n' > docs-policies.jsonEn la primera terminal, inicie PMS y déjelo en ejecución:
./bin/speedle-pms --store-type file --filestore-loc ./docs-policies.json --endpoint 127.0.0.1:6733 --grpc-endpoint 127.0.0.1:0 --insecure=trueEn la segunda terminal, inicie ADS y déjelo en ejecución:
./bin/speedle-ads --store-type file --filestore-loc ./docs-policies.json --endpoint 127.0.0.1:6734 --grpc-endpoint 127.0.0.1:0 --insecure=trueEjecute los comandos restantes en una tercera terminal. PMS y ADS comparten el archivo; watch propaga los cambios de forma asíncrona. Confirme que la actualización deseada ha llegado a ADS antes de depender de ella. --insecure=true es solo para este ejemplo en loopback; consulte seguridad para conexiones de producción. Indicar direcciones explícitas de PMS evita modificar ~/.spctlconfig. Detenga ambas terminales de servidor con Ctrl-C al terminar.
Registrar e invocar la función
La siguiente definición deshabilita el almacenamiento en caché de resultados para que las decisiones reflejen cada solicitud. Crea el servicio antes de su política.
cat > function.json <<'JSON'
{"name":"isValid","funcURL":"http://127.0.0.1:23456/func/isValid","resultCachable":false}
JSON
./bin/spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create function --json-file function.json
./bin/spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create service service1
./bin/spctl --pms-endpoint http://127.0.0.1:6733/policy-mgmt/v1/ create policy -c "grant user Ally access library if isValid(attr1)" --service-name service1Las siguientes solicitudes devuelven Conceder (200 está dentro del rango) y Denegar (50 está fuera de él):
curl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"principals":[{"type":"user","name":"Ally"}]},"serviceName":"service1","resource":"library","action":"access","attributes":[{"name":"attr1","type":"numeric","value":200}]}' \
http://127.0.0.1:6734/authz-check/v1/is-allowedcurl --fail --silent --show-error -H 'Content-Type: application/json' \
-d '{"subject":{"principals":[{"type":"user","name":"Ally"}]},"serviceName":"service1","resource":"library","action":"access","attributes":[{"name":"attr1","type":"numeric","value":50}]}' \
http://127.0.0.1:6734/authz-check/v1/is-allowedTLS y almacenamiento en caché
Para un endpoint TLS, genere certificados de prueba locales nuevos y luego inicie la función con go run ./website/tests/examples/custom-function --cert ./tls/server.crt --key ./tls/server.key. Registre funcURL como https://localhost:23456/func/isValid y establezca ca con el contenido del certificado PEM de tls/ca.crt, no con su ruta de archivo. Los certificados de producción deben cubrir el host real. No se requiere ningún certificado vencido codificado de forma fija o clave privada.
resultCachable es false por defecto; resultTTL es el tiempo de vida en segundos cuando la caché está habilitada. Habilite la caché solo si el resultado sigue siendo correcto para ese período y conjunto de parámetros. El estado externo, como el estado de una cuenta, puede cambiar antes de que expire el TTL. Utilice la gestión de funciones de PMS para administrar definiciones; la invocación de funciones externas no está disponible en el Playground del navegador.
Ejemplo: privilegios permanentes cero (ZSP)
Las condiciones SPDL y las funciones personalizadas pueden admitir un patrón de privilegios permanentes cero (ZSP) con credenciales de corta duración y alcance limitado, junto con la verificación de tokens y el consumo atómico en la aplicación. El ejemplo ejecutable de ZSP para Speedle autohospedado combina una función personalizada con comprobaciones de políticas relativas a la vida útil de un token, la ventana de validez y su estado no utilizado. La emisión, verificación y consumo atómico de tokens continúan siendo responsabilidad de la aplicación.