shrub/pkg/arvo/ted/get-gcp-jwt.hoon

135 lines
3.9 KiB
Plaintext
Raw Normal View History

:: 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)
--