(in-package #:nekod.socket) ;;; Security boundary — capability-based access control for Docker socket operations (defvar *capability-registry* (make-hash-table :test #'equal)) (defstruct capability (name "" :type string) (method "" :type string) (path-prefix "" :type string) (granted nil :type boolean)) (defun register-capability (name method path-prefix &optional (granted t)) "Register a named capability for Docker API access." (setf (gethash name *capability-registry*) (make-capability :name name :method method :path-prefix path-prefix :granted granted))) (defun init-default-capabilities () "Initialize the default capability set." (register-capability "container.read" "GET" "/containers") (register-capability "container.start" "POST" "/containers") (register-capability "container.stop" "POST" "/containers") (register-capability "network.read" "GET" "/networks") (register-capability "network.create" "POST" "/networks/create") (register-capability "network.delete" "DELETE" "/networks") (register-capability "events.subscribe" "GET" "/events") (register-capability "system.ping" "GET" "/_ping") (register-capability "system.version" "GET" "/version")) (defun authorize-operation (method path) "Check if an operation (method + path) is authorized by any granted capability. Returns the matching capability name or signals policy-denied." (maphash (lambda (name cap) (when (and (capability-granted cap) (string= method (capability-method cap)) (or (string= path (capability-path-prefix cap)) (and (> (length path) (length (capability-path-prefix cap))) (string= (capability-path-prefix cap) (subseq path 0 (length (capability-path-prefix cap))))))) (return-from authorize-operation name))) *capability-registry*) (error 'nekod:policy-denied :message (format nil "~a ~a" method path) :operation (format nil "~a ~a" method path))) (defun revoke-capability (name) "Revoke a previously granted capability." (let ((cap (gethash name *capability-registry*))) (when cap (setf (capability-granted cap) nil)))) (defun grant-capability (name) "Grant a capability." (let ((cap (gethash name *capability-registry*))) (when cap (setf (capability-granted cap) t)))) (defun validate-socket-path (path) "Ensure socket path is a known safe value. Rejects arbitrary paths." (unless (member path '("/var/run/docker.sock" "/run/docker.sock" "/var/run/podman/podman.sock") :test #'string=) (error 'nekod:policy-denied :message (format nil "Untrusted socket path: ~a" path) :operation "socket-connect")))