File size: 3,033 Bytes
3b70664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
(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")))