Update docs. (refs #2328)
[arvados.git] / services / keep / src / keep / perms.go
1 /*
2 Permissions management on Arvados locator hashes.
3
4 The permissions structure for Arvados is as follows (from
5 https://arvados.org/issues/2328)
6
7 A Keep locator string has the following format:
8
9     [hash]+[size]+A[signature]@[timestamp]
10
11 The "signature" string here is a cryptographic hash, expressed as a
12 string of hexadecimal digits, and timestamp is a 32-bit Unix timestamp
13 expressed as a hexadecimal number.  e.g.:
14
15     acbd18db4cc2f85cedef654fccc4a4d8+3+A257f3f5f5f0a4e4626a18fc74bd42ec34dcb228a@7fffffff
16
17 The signature represents a guarantee that this locator was generated
18 by either Keep or the API server for the user with the supplied API
19 token.  If a request to Keep includes a locator with a valid signature
20 and is accompanied by the proper API token, the user has permission to
21 perform any action on that object (GET, PUT or DELETE).
22
23 The signature may be generated either by Keep (after the user writes a
24 block) or by the API server (if the user has can_read permission on
25 the specified object). Keep and API server share a secret that is used
26 to generate signatures.
27
28 To verify a permission hint, Keep generates a new hint for the
29 requested object (using the locator string, the timestamp, the
30 permission secret and the user's API token, which must appear in the
31 request headers) and compares it against the hint included in the
32 request. If the permissions do not match, or if the API token is not
33 present, Keep returns a 401 error.
34 */
35
36 package main
37
38 import (
39         "crypto/hmac"
40         "crypto/sha1"
41         "fmt"
42         "regexp"
43         "strings"
44 )
45
46 // The PermissionSecret is the secret key used to generate SHA1
47 // digests for permission hints. apiserver and Keep must use the same
48 // key.
49 var PermissionSecret []byte
50
51 // GeneratePerms returns a string representing the signed permission
52 // hint for the blob identified by blob_hash, api_token and timestamp.
53 func GeneratePerms(blob_hash string, api_token string, timestamp string) string {
54         hmac := hmac.New(sha1.New, PermissionSecret)
55         hmac.Write([]byte(blob_hash))
56         hmac.Write([]byte("@"))
57         hmac.Write([]byte(api_token))
58         hmac.Write([]byte("@"))
59         hmac.Write([]byte(timestamp))
60         digest := hmac.Sum(nil)
61         return fmt.Sprintf("%x", digest)
62 }
63
64 // SignLocator takes a blob_locator, an api_token and a timestamp, and
65 // returns a signed locator string.
66 func SignLocator(blob_locator string, api_token string, timestamp string) string {
67         // Extract the hash from the blob locator, omitting any size hint that may be present.
68         blob_hash := strings.Split(blob_locator, "+")[0]
69         // Return the signed locator string.
70         return blob_locator + "+A" + GeneratePerms(blob_hash, api_token, timestamp) + "@" + timestamp
71 }
72
73 // VerifySignature returns true if the signature on the signed_locator
74 // can be verified using the given api_token.
75 func VerifySignature(signed_locator string, api_token string) bool {
76         if re, err := regexp.Compile(`^(.*)\+A(.*)@(.*)$`); err == nil {
77                 if matches := re.FindStringSubmatch(signed_locator); matches != nil {
78                         blob_locator := matches[1]
79                         timestamp := matches[3]
80                         return signed_locator == SignLocator(blob_locator, api_token, timestamp)
81                 }
82         }
83         return false
84 }