From 8af52772cc93b7bcfa555686ea4e9e82acab756a Mon Sep 17 00:00:00 2001 From: Joe Bryan Date: Thu, 28 Mar 2019 11:19:35 -0700 Subject: [PATCH] adds manual oauth2 to :dns (for |gcloud provider) --- app/dns.hoon | 304 ++++++++++++++++++++++++++++++++++++++--- gen/dns/authority.hoon | 2 +- sur/dns.hoon | 2 +- tests/app/dns.hoon | 10 +- 4 files changed, 296 insertions(+), 22 deletions(-) diff --git a/app/dns.hoon b/app/dns.hoon index d39a02522..ca2d6c762 100644 --- a/app/dns.hoon +++ b/app/dns.hoon @@ -10,8 +10,10 @@ [%hall-action %phrase audience:hall (list speech:hall)] == +$ card - $% [%request wire request:http outbound-config:http-client] + $% [%connect wire =binding:http-server app=term] + [%http-response =http-event:http] [%poke wire dock poke] + [%request wire request:http outbound-config:http-client] [%rule wire %turf %put turf] [%wait wire @da] == @@ -47,6 +49,101 @@ ?~ t.hot (rap 3 [i.hot out]) $(out [sep i.hot out], hot t.hot) +:: +local-uri: XX +:: +++ local-uri + |= [our=ship =path] + ^- @t + =/ =hart:eyre .^(hart:eyre %r /(scot %p our)/host/real) + (crip (en-purl:html [hart [~ path] ~])) +:: +oauth2-config: as one would expect +:: ++$ oauth2-config + $: auth-url=@t + exchange-url=@t + domain=turf + initial-path=path + redirect-path=path + scopes=(list @t) + == +:: +:: +oauth2: library core +:: +++ oauth2 + |_ [our=@p now=@da config=oauth2-config] + :: + ++ code + ^- @t + %- crip + +:(scow %p .^(@p %j /(scot %p our)/code/(scot %da now)/(scot %p our))) + :: + :: to initialize these values: |init-oauth2 /com/googleapis + :: + ++ oauth2-secrets + ^- [client-id=@t client-secret=@t] + =; =wain + ?> ?=([@t @t ~] wain) + [i.wain i.t.wain] + :: + %- to-wain:format + %- need + %+ de:crub:crypto code + %+ slav %uw + .^(@ %cx :(weld /(scot %p our)/home/(scot %da now)/sec domain.config /atom)) + :: + ++ initial-uri (local-uri our initial-path.config) + ++ redirect-uri (local-uri our redirect-path.config) + :: + ++ redirect-to-provider + ^- @t + =/ url (need (de-purl:html auth-url.config)) + =. r.url + :* ['access_type' 'offline'] + ['response_type' 'code'] + ['prompt' 'consent'] + ['client_id' client-id:oauth2-secrets] + ['redirect_uri' redirect-uri] + ['scope' (join ' ' scopes.config)] + r.url + == + (crip (en-purl:html url)) + :: + ++ retrieve-access-token + |= code=@t + ^- request:http + =/ hed + :~ ['Accept' 'application/json'] + ['Content-Type' 'application/x-www-form-urlencoded'] + == + =/ bod + %- some %- as-octt:mimes:html + %- tail %- tail:en-purl:html + :~ ['client_id' client-id:oauth2-secrets] + :: note: required, unused parameter + :: + ['redirect_uri' redirect-uri] + ['client_secret' client-secret:oauth2-secrets] + ['grant_type' 'authorization_code'] + ['code' code] + == + [%'POST' exchange-url.config hed bod] + :: + ++ parse-token-response + |= =octs + ^- (unit [access=@t expires=@u refresh=@t]) + %. q.octs + ;~ biff + de-json:html + =, dejs-soft:format + (ot 'access_token'^so 'expires_in'^ni 'refresh_token'^so ~) + == + :: XX implement + :: + ++ refresh-token !! + -- +-- +:: +=> |% :: +name: fully-qualified domain name for :ship :: ++ name @@ -282,6 +379,17 @@ :: |gcloud: GCP provider :: ++ gcloud + => |% + ++ headers + |= aut=authority + ?> ?=(%gcloud -.pro.aut) + ?. ?=(^ auth.pro.aut) + ~| %gcloud-missing-auth !! + %- ~(gas by *math:eyre) + :~ ['Content-Type' ['application/json' ~]] + ['Authorization' [`@t`(cat 3 'Bearer ' access.u.auth.pro.aut) ~]] + == + -- |_ aut=authority :: +base: provider service endpoint :: @@ -294,7 +402,7 @@ ^- hiss:eyre ?> ?=(%gcloud -.pro.aut) :- (endpoint base /[project.pro.aut]/['managedZones']/[zone.pro.aut]) - [%get ~ ~] + [%get (headers aut) ~] :: +record: JSON-formatted provider-specific dns record :: ++ record @@ -326,8 +434,6 @@ =/ url=purl %+ endpoint base /[project.pro.aut]/['managedZones']/[zone.pro.aut]/changes - =/ hed=math - (my content-type+['application/json' ~] ~) =/ bod=octs %- as-octt:mimes:html %- en-json:html @@ -336,7 +442,7 @@ :- ['additions' %a (record him tar) ~] ?~ pre ~ [['deletions' %a (record him tar.u.pre) ~] ~] - [url %post hed `bod] + [url %post (headers aut) `bod] :: +existing: list existing records stored by provider :: ++ existing @@ -347,9 +453,9 @@ =/ url=purl %+ endpoint base /[project.pro.aut]/['managedZones']/[zone.pro.aut]/rrsets - =/ hed=math - ?~ page ~ - (~(put by *math) 'pageToken' [u.page]~) + =/ hed=math (headers aut) + =? hed ?=(^ page) + (~(put by hed) 'pageToken' [u.page]~) [url %get hed ~] :: +parse-list: existing records stored by provider :: @@ -416,6 +522,20 @@ :: +this: is sparta :: ++ this . +:: |oauth2-core: configured oauth functionality (for |gcloud only) +:: +++ oauth2-core + =/ =oauth2-config + :* auth-url='https://accounts.google.com/o/oauth2/v2/auth' + exchange-url='https://www.googleapis.com/oauth2/v4/token' + domain=/com/googleapis + redirect-path=/dns/oauth + initial-path=/dns/oauth/result + :~ 'https://www.googleapis.com/auth/ndev.clouddns.readwrite' + 'https://www.googleapis.com/auth/cloud-platform.read-only' + == == + ~(. oauth2 our.bow now.bow oauth2-config) +:: :: +notify: send :hall notification :: ++ notify @@ -479,6 +599,20 @@ (http-cancel:(tell him) t.t.t.wire) (http-response:(tell him) t.t.t.wire (to-httr:http-client +.response)) == +:: +++ poke-handle-http-request + |= =inbound-request:http-server + ^- (quip move _this) + ?~ nem + ~& :* %not-an-authority + %http-request + => inbound-request + [authenticated secure address [method url]:request] + == + =/ =move + [ost.bow %http-response %start [%403 ~] ~ %.y] + [[move ~] this] + abet:(~(http-request bind u.nem) inbound-request) :: +wake: timer callback :: ++ wake @@ -511,7 +645,7 @@ %authority ~| %authority-reset-wat-do ?< ?=(^ nem) - abet:(init:bind aut.com 1) + abet:(pre-init:bind aut.com) :: create binding (if authority) and forward request :: :: [%bind for=ship him=ship target] @@ -586,8 +720,22 @@ ^- (quip move _this) ?^ old [~ this(+<+ u.old)] - :: XX print :dns|ip config instructions for stars? + :: XX this binding should be deleted when we're done + :: but that would require tracking bones :: + =/ =move + [ost.bow %connect /dns/oauth [~ /dns/oauth] %dns] + :: XX also print :dns|ip config instructions for stars? + :: + [[move ~] this] +:: +++ bound + |= [=wire accepted=? =binding:http-server] + ?: accepted + [~ this] + :: XX better error message + :: + ~& [%dns-http-path-binding-failed +<] [~ this] :: |bind: acting as zone authority :: @@ -632,6 +780,10 @@ =/ try (slav %ud i.t.wire) ?+ t.t.wire ~&([%bind %unknown-crash wire] this) + :: + [%oath %access ~] + ~& %do-the-oauth-thing-again + this :: [%confirm ~] =. try +(try) @@ -654,6 +806,26 @@ =/ try (slav %ud i.t.wire) ?+ t.t.wire ~&([%bind %unknown-response wire rep] this) + :: response providing oauth access token (|gcloud only) + :: + [%oauth %access ~] + ?> ?=(%gcloud -.pro.aut.nam) + :: XX save access/refresh tokens + :: + ?. =(200 p.rep) + ~& [%oauth-failed p.rep] + this + ?~ r.rep + ~& [%oauth-failed %no-body] + this + =/ data (parse-token-response:oauth2 u.r.rep) + ?~ data + ~& [%oauth-failed %invalid-body u.r.rep] + this + =. auth.pro.aut.nam (some [access refresh]:u.data) + :: XX use expiry to set refresh timer + :: + (init 1) :: response confirming a valid nameserver config :: [%confirm ~] @@ -678,6 +850,8 @@ =/ him=ship (slav %p i.t.t.t.wire) =/ for=ship (slav %p i.t.t.t.t.t.wire) =/ id + :: XX move into provider interface + :: ?. ?=(%fcloud -.pro.aut.nam) ~. ~| [%authority-create-confirm-id rep] ?> ?=(^ r.rep) @@ -698,6 +872,86 @@ this (restore u.r.rep) == + :: +http-request: act as server for oauth redirects (|gcloud only) + :: + ++ http-request + |= =inbound-request:http-server + ^+ this + ?> ?=(%gcloud -.pro.aut.nam) + :: + =/ parsed=(unit (pair pork:eyre quay:eyre)) + %+ rush + url.request.inbound-request + ;~(plug ;~(pose apat:de-purl:html (easy *pork:eyre)) yque:de-purl:html) + :: + ?. ?=(^ parsed) + ~| [%invalid-url url.request.inbound-request] !! + =* url q.p.u.parsed + =* ext p.p.u.parsed + =* params q.u.parsed + :: + ?+ url + (emit %http-response %start [%404 ~] ~ %.y) + :: + [%dns %oauth ~] + =/ link (trip redirect-to-provider:oauth2-core) + =/ bod=(unit octs) + %- some + %- as-octt:mimes:html + %- en-xml:html + ;html + ;head + ;title: :dns oauth + == + ;body + ;p make sure that the oauth credential is configured + with a redirect uri of {(trip redirect-uri:oauth2-core)} + == + ;a(href link): {link} + == + == + (emit %http-response %start [%200 ~] bod %.y) + :: + [%dns %oauth %result ~] + =/ code (~(got by (my params)) %code) + :: XX make path configurable + :: + =/ hed [['Location' '/dns/oauth/success'] ~] + :: XX y no tisdot tho + :: + %- =< emit + %: emit + %request + (http-wire 1 /oauth/access) + (retrieve-access-token:oauth2-core code) + *outbound-config:http-client + == + :: XX don't redirect to success until above request + :: comes back successfully + :: + [%http-response %start [%301 hed] ~ %.y] + :: + [%dns %oauth %success ~] + =/ bod=(unit octs) + %- some + %- as-octt:mimes:html + %- en-xml:html + ;html + ;head + ;title: :dns oauth + == + ;body + ;p: you may close the browser window + ;p + ;span: XX remove me + :: XX make path configurable + :: + ;a(href "/dns/oauth"): again + == + == + == + (emit %http-response %start [%201 ~] bod %.y) + == :: +retry: re-attempt http request after timer :: ++ retry @@ -709,7 +963,7 @@ ~&([%bind %unknown-retry wire] this) :: [%confirm ~] - (init aut.nam try) + (init try) :: [%create @ %for @ ~] =/ him=ship (slav %p i.t.t.t.wire) @@ -720,12 +974,26 @@ =* page i.t.t.t.wire (update ?~(page ~ `page) try) == + :: +pre-init: gross + :: + ++ pre-init + |= aut=authority + =. nam [aut ~ ~ ~] + :: XX move this into the provider interface + :: + ?: ?& ?=(%gcloud -.pro.aut) + ?=(~ auth.pro.aut) + == + ~& %do-the-oauth-thing + ~& initial-uri:oauth2-core + this + (init 1) :: +init: establish zone authority (request confirmation) :: ++ init - |= [aut=authority try=@ud] - %- emit(nam [aut ~ ~ ~]) - (request (http-wire try /confirm) zone:(provider aut)) + |= try=@ud + %- emit + (request (http-wire try /confirm) zone:(provider aut.nam)) :: +update: retrieve existing remote nameserver records :: ++ update @@ -748,7 +1016,7 @@ |- ^+ this ?~ dat ?~(page this (update page 1)) - =/ nob=bound [now.bow id.i.dat tar.i.dat ~] + =/ nob=^bound [now.bow id.i.dat tar.i.dat ~] $(dat t.dat, bon.nam (~(put by bon.nam) ship.i.dat nob)) :: +create: bind :him, on behalf of :for :: @@ -790,7 +1058,7 @@ =/ =wire (http-wire try /create/(scot %p him)/for/(scot %p for)) =/ pre=(unit [id=@ta tar=target]) - =/ bon=(unit bound) (~(get by bon.nam) him) + =/ bon=(unit ^bound) (~(get by bon.nam) him) ?~(bon ~ `[id.u.bon cur.u.bon]) =/ req=hiss:eyre (create:(provider aut.nam) him tar pre) @@ -811,9 +1079,9 @@ ++ confirm |= [for=ship him=ship id=@ta] =/ tar=target (~(got by pen.nam) him) - =/ bon=(unit bound) + =/ bon=(unit ^bound) (~(get by bon.nam) him) - =/ nob=bound + =/ nob=^bound [now.bow id tar ?~(bon ~ [[wen.u.bon cur.u.bon] hit.u.bon])] =: pen.nam (~(del by pen.nam) him) bon.nam (~(put by bon.nam) him nob) diff --git a/gen/dns/authority.hoon b/gen/dns/authority.hoon index c2c3d7cab..d26009d3d 100644 --- a/gen/dns/authority.hoon +++ b/gen/dns/authority.hoon @@ -27,4 +27,4 @@ %+ parse urs:ab |= zone=@ta %- produce -[%dns-command %authority [p.hot %gcloud project zone]] +[%dns-command %authority [p.hot %gcloud project zone ~]] diff --git a/sur/dns.hoon b/sur/dns.hoon index de600b25e..4e969b29a 100644 --- a/sur/dns.hoon +++ b/sur/dns.hoon @@ -3,7 +3,7 @@ :: +$ provider $% [%fcloud zone=@ta auth=[email=@t key=@t]] - [%gcloud project=@ta zone=@ta] + [%gcloud project=@ta zone=@ta auth=(unit [access=@t refresh=@t])] == :: +authority: responsibility for a DNS zone :: diff --git a/tests/app/dns.hoon b/tests/app/dns.hoon index 7a5a453fc..5c2740271 100644 --- a/tests/app/dns.hoon +++ b/tests/app/dns.hoon @@ -7,8 +7,14 @@ :: tests that :dns preps without moves :: ++ test-prep - =^ moves app (~(prep app *bowl:gall *state:app) ~) + :: .our explicitly set to not-a-galaxy to avoid failing %jael scry + :: (can't control the scry product without virtualizing) + :: + =/ bow=bowl:gall =>(*bowl:gall .(our ~marzod)) + =^ moves app (~(prep app bow *state:app) ~) %+ expect-eq - !> *(list move:app) + !> ^- (list move:app) + :~ [ost.bow %connect /dns/oauth [~ /dns/oauth] %dns] + == !> moves --