| (in-package #:nekod.socket)
|
|
|
|
|
|
|
| (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")))
|
|
|