mirror of
https://github.com/urbit/shrub.git
synced 2024-12-30 07:35:19 +03:00
c1b259af5b
Options here were: make the get-token thread try to return a unit, or write a different thread to check whether GCP Storage seemed to be configured and poll on that thread, or move the whole thing into a Gall app. The compromise between time-to-implement and overall cleanliness seemed to be to write a different thread that just checks whether the settings fields have been poked. Unfortunately this means GcpManager is now a somewhat hefty JavaScript state machine. Took out the logic to check whether S3 was configured in GcpManager, since it was really only there to prevent spamming the console with 500s. If you have both S3 and GCP Storage configured for some reason, you will now use GCP per the logic in useStorage.
145 lines
4.1 KiB
Plaintext
145 lines
4.1 KiB
Plaintext
:: Gets a Google Storage access token.
|
|
::
|
|
:: This thread produces a pair of [access-key expires-in], where
|
|
:: access-key is a @t that can be used as a bearer token to talk
|
|
:: to the GCP Storage API on behalf of some service account, and
|
|
:: expires-in is a @dr after which the token will stop working and
|
|
:: need to be refreshed.
|
|
::
|
|
:: It expects settings-store to contain relevant fields from
|
|
:: a GCP service account JSON file, generally as poked by
|
|
:: 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 responds with a bearer token and expiry.
|
|
::
|
|
::
|
|
/- gcp, spider, settings
|
|
/+ jose, pkcs, primitive-rsa, strandio
|
|
=, strand=strand:spider
|
|
=, rsa=primitive-rsa
|
|
^- 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)
|
|
=* scope
|
|
'https://www.googleapis.com/auth/devstorage.read_write'
|
|
=/ jot=@t
|
|
(make-jwt key kid iss scope aud now.bowl)
|
|
;< =token:gcp bind:m
|
|
(get-access-token jot aud)
|
|
(pure:m !>(token))
|
|
::
|
|
++ 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 (rap 3 %gcp-missing- 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:pkcs
|
|
need
|
|
==
|
|
:: construct and return a self-signed JWT issued now, expiring in ~h1.
|
|
:: TODO: maybe move this into lib/jose/hoon
|
|
::
|
|
++ make-jwt
|
|
|= [=key:rsa kid=@t iss=@t scope=@t aud=@t iat=@da]
|
|
^- @t
|
|
=/ job=json
|
|
=, enjs:format
|
|
%^ sign:jws:jose key
|
|
:: the JWT's "header"
|
|
%: pairs
|
|
alg+s+'RS256'
|
|
typ+s+'JWT'
|
|
kid+s+kid
|
|
~
|
|
==
|
|
:: the JWT's "payload"
|
|
%: pairs
|
|
iss+s+iss
|
|
sub+s+iss :: per g.co, use iss for sub
|
|
scope+s+scope
|
|
aud+s+aud
|
|
iat+(sect iat)
|
|
exp+(sect (add iat ~h1))
|
|
~
|
|
==
|
|
=/ [pod=@t pad=@t sig=@t]
|
|
=, dejs:format
|
|
((ot 'protected'^so 'payload'^so 'signature'^so ~) job)
|
|
(rap 3 (join '.' `(list @t)`~[pod pad sig]))
|
|
:: RPC to get an access token. Probably only works with Google.
|
|
:: Described at:
|
|
:: https://developers.google.com/identity/protocols/oauth2/service-account
|
|
::
|
|
++ get-access-token
|
|
|= [jot=@t url=@t]
|
|
=/ m (strand ,token:gcp) ^- 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:format
|
|
:- '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 %gcp-no-response ~)
|
|
=/ body=@t q.data.u.full-file.rep
|
|
=/ jon=(unit json) (de-json:html body)
|
|
?~ jon
|
|
~| body
|
|
(strand-fail:strandio %gcp-bad-body ~)
|
|
=* job u.jon
|
|
~| job
|
|
=, dejs:format
|
|
=/ [typ=@t =token:gcp]
|
|
%. job
|
|
%: ot
|
|
'token_type'^so
|
|
'access_token'^so
|
|
'expires_in'^(cu |=(a=@ (mul a ~s1)) ni)
|
|
~
|
|
==
|
|
?> =('Bearer' typ)
|
|
(pure:m token)
|
|
--
|