diff --git a/pkg/arvo/ted/get-gcp-jwt.hoon b/pkg/arvo/ted/get-gcp-jwt.hoon new file mode 100644 index 000000000..82fd26acc --- /dev/null +++ b/pkg/arvo/ted/get-gcp-jwt.hoon @@ -0,0 +1,134 @@ +:: Thread that gets a JWT for use with Google Storage. +:: +:: This thread expects settings-store to contain relevant fields from a GCP +:: service account JSON file, e.g. as poked by urbit/sh/poke-gcp-account-json. +:: Specifically, it depends on the `token_uri`, `client_email`, +:: `private_key_id`, and `private_key` fields. If these fields are not in +:: settings-store at the time the thread is run, it will fail. +:: +:: The thread works by first constructing a self-signed JWT using the fields +:: in settings-store. Then, it sends this JWT to the specified token URI +:: (usually https://oauth2.googleapis.com/token), which gives us a JWT signed +:: by Google. This token can then be used as a bearer token for requests to +:: Google Storage. +:: +:: The returned token has an expiration time of 60 minutes after the time at +:: which the thread was called. +:: +:: +/- spider, settings +/+ jose, pkcs, strandio +=, strand=strand:spider +=, format +=, jose +=, pkcs +^- thread:spider +|^ +|= * +=/ m (strand ,vase) +^- form:m +;< =bowl:spider bind:m get-bowl:strandio +;< iss=@t bind:m (read-setting %client-email) +;< =key:rsa bind:m read-private-key +;< kid=@t bind:m (read-setting %private-key-id) +;< aud=@t bind:m (read-setting %token-uri) +=/ sot=@t + %: self-jwt + key kid iss + 'https://www.googleapis.com/auth/devstorage.editor' + aud now.bowl + == +;< jot=@t bind:m (sign-jwt sot aud) +(pure:m !>(jot)) +:: +++ read-setting + |= key=term + =/ m (strand @t) ^- form:m + ;< has=? bind:m + (scry:strandio ? /gx/settings-store/has-entry/gcp-store/[key]/noun) + ?. has + (strand-fail:strandio %no-setting key ~) + ;< =data:settings bind:m + %+ scry:strandio + data:settings + /gx/settings-store/entry/gcp-store/[key]/settings-data + ?> ?=([%entry %s @] data) + (pure:m p.val.data) +:: +++ read-private-key + =/ m (strand ,key:rsa) ^- form:m + ;< dat=@t bind:m (read-setting %private-key) + %- pure:m + %. dat + ;: cork + to-wain:format + ring:de:pem:pkcs8 + need + == +:: construct and return a self-signed JWT issued now, expiring in ~h1. +:: TODO: maybe move this into lib/jose/hoon +:: +++ self-jwt + |= [=key:rsa kid=@t iss=@t scope=@t aud=@t iat=@da] + ^- @t + =/ job=json + %^ sign:jws key + :: the JWT's "header" + %: pairs:enjs + alg+s+'RS256' + typ+s+'JWT' + kid+s+kid + ~ + == + :: the JWT's "payload" + %: pairs:enjs + iss+s+iss + sub+s+iss :: per g.co, use iss for sub + scope+s+scope + aud+s+aud + iat+(sect:enjs iat) + exp+(sect:enjs (add iat ~h1)) + ~ + == + ?> ?=([%o *] job) + =* mep p.job + =+ :~ pod=(sa:dejs (~(got by mep) 'protected')) + pad=(sa:dejs (~(got by mep) 'payload')) + sig=(sa:dejs (~(got by mep) 'signature')) + == + %- crip :: XX + :(weld pod "." pad "." sig) +:: RPC to get a signed JWT. Probably only works with Google. +:: Described at: +:: https://developers.google.com/identity/protocols/oauth2/service-account +:: +++ sign-jwt + |= [jot=@t url=@t] + =/ m (strand @t) ^- form:m + ;< ~ bind:m + %: send-request:strandio + method=%'POST' + url=url + header-list=['Content-Type'^'application/json' ~] + ^= body + %- some %- as-octt:mimes:html + %- en-json:html + %: pairs:enjs + ['grant_type' s+'urn:ietf:params:oauth:grant-type:jwt-bearer'] + assertion+s+jot + ~ + == + == + ;< rep=client-response:iris bind:m + take-client-response:strandio + ?> ?=(%finished -.rep) + ?~ full-file.rep + (strand-fail:strandio %no-response ~) + =/ body=@t q.data.u.full-file.rep + =/ jon=(unit json) (de-json:html body) + ?~ jon + (strand-fail:strandio %bad-body ~[body]) + ?. ?=([%o [[%'id_token' %s @] ~ ~]] +.jon) + (strand-fail:strandio %bad-json ~[body]) + (pure:m p.q.n.p.u.jon) +--