From 62c5d3761db461bbd5e7f9d879983fade263458b Mon Sep 17 00:00:00 2001 From: Marcos Nils Date: Thu, 11 May 2017 10:34:16 -0300 Subject: [PATCH] Add support for setting stacks when creating session (#138) * Add support for setting stacks when creating session * Add exec endpoint and move dns stuff to another package * Rename command and status code --- api.go | 87 +------------------------------------ handlers/dns.go | 93 ++++++++++++++++++++++++++++++++++++++++ handlers/exec.go | 40 +++++++++++++++++ handlers/new_session.go | 3 +- services/docker.go | 17 ++++++++ services/session.go | 1 + www/assets/button.png | Bin 0 -> 3822 bytes www/bypass.html | 17 ++++++++ www/welcome.html | 15 +++++++ 9 files changed, 187 insertions(+), 86 deletions(-) create mode 100644 handlers/dns.go create mode 100644 handlers/exec.go create mode 100644 www/assets/button.png diff --git a/api.go b/api.go index 8f7cac9..3d4a151 100644 --- a/api.go +++ b/api.go @@ -4,10 +4,8 @@ import ( "crypto/tls" "fmt" "log" - "net" "net/http" "os" - "regexp" "strings" gh "github.com/gorilla/handlers" @@ -28,7 +26,7 @@ func main() { bypassCaptcha := len(os.Getenv("GOOGLE_RECAPTCHA_DISABLED")) > 0 // Start the DNS server - dns.HandleFunc(".", handleDnsRequest) + dns.HandleFunc(".", handlers.DnsRequest) udpDnsServer := &dns.Server{Addr: ":53", Net: "udp"} go func() { err := udpDnsServer.ListenAndServe() @@ -72,6 +70,7 @@ func main() { corsRouter.HandleFunc("/sessions/{sessionId}", handlers.GetSession).Methods("GET") corsRouter.HandleFunc("/sessions/{sessionId}/instances", handlers.NewInstance).Methods("POST") corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", handlers.DeleteInstance).Methods("DELETE") + corsRouter.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/exec", handlers.Exec).Methods("POST") r.HandleFunc("/sessions/{sessionId}/instances/{instanceName}/keys", handlers.SetKeys).Methods("POST") h := func(w http.ResponseWriter, r *http.Request) { @@ -137,85 +136,3 @@ func main() { } log.Fatal(s.ListenAndServeTLS("", "")) } - -var dnsFilter = regexp.MustCompile(`pwd[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}`) - -func handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) { - if len(r.Question) > 0 && dnsFilter.MatchString(r.Question[0].Name) { - // this is something we know about and we should try to handle - question := r.Question[0].Name - domainChunks := strings.Split(question, ".") - tldChunks := strings.Split(strings.TrimPrefix(domainChunks[0], "pwd"), "-") - ip := strings.Replace(tldChunks[0], "_", ".", -1) - - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative = true - m.RecursionAvailable = true - a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, ip)) - if err != nil { - log.Fatal(err) - } - m.Answer = append(m.Answer, a) - w.WriteMsg(m) - return - } else { - if len(r.Question) > 0 { - question := r.Question[0].Name - - if question == "localhost." { - log.Printf("Not a PWD host. Asked for [localhost.] returning automatically [127.0.0.1]\n") - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative = true - m.RecursionAvailable = true - a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A 127.0.0.1", question)) - if err != nil { - log.Fatal(err) - } - m.Answer = append(m.Answer, a) - w.WriteMsg(m) - return - } - - log.Printf("Not a PWD host. Looking up [%s]\n", question) - ips, err := net.LookupIP(question) - if err != nil { - // we have no information about this and we are not a recursive dns server, so we just fail so the client can fallback to the next dns server it has configured - w.Close() - // dns.HandleFailed(w, r) - return - } - log.Printf("Not a PWD host. Looking up [%s] got [%s]\n", question, ips) - m := new(dns.Msg) - m.SetReply(r) - m.Authoritative = true - m.RecursionAvailable = true - for _, ip := range ips { - ipv4 := ip.To4() - if ipv4 == nil { - a, err := dns.NewRR(fmt.Sprintf("%s 60 IN AAAA %s", question, ip.String())) - if err != nil { - log.Fatal(err) - } - m.Answer = append(m.Answer, a) - } else { - a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, ipv4.String())) - if err != nil { - log.Fatal(err) - } - m.Answer = append(m.Answer, a) - } - } - w.WriteMsg(m) - return - - } else { - log.Printf("Not a PWD host. Got DNS without any question\n") - // we have no information about this and we are not a recursive dns server, so we just fail so the client can fallback to the next dns server it has configured - w.Close() - // dns.HandleFailed(w, r) - return - } - } -} diff --git a/handlers/dns.go b/handlers/dns.go new file mode 100644 index 0000000..138690a --- /dev/null +++ b/handlers/dns.go @@ -0,0 +1,93 @@ +package handlers + +import ( + "fmt" + "log" + "net" + "regexp" + "strings" + + "github.com/miekg/dns" +) + +var dnsFilter = regexp.MustCompile(`pwd[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}_[0-9]{1,3}`) + +func DnsRequest(w dns.ResponseWriter, r *dns.Msg) { + if len(r.Question) > 0 && dnsFilter.MatchString(r.Question[0].Name) { + // this is something we know about and we should try to handle + question := r.Question[0].Name + domainChunks := strings.Split(question, ".") + tldChunks := strings.Split(strings.TrimPrefix(domainChunks[0], "pwd"), "-") + ip := strings.Replace(tldChunks[0], "_", ".", -1) + + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + m.RecursionAvailable = true + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, ip)) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + w.WriteMsg(m) + return + } else { + if len(r.Question) > 0 { + question := r.Question[0].Name + + if question == "localhost." { + log.Printf("Not a PWD host. Asked for [localhost.] returning automatically [127.0.0.1]\n") + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + m.RecursionAvailable = true + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A 127.0.0.1", question)) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + w.WriteMsg(m) + return + } + + log.Printf("Not a PWD host. Looking up [%s]\n", question) + ips, err := net.LookupIP(question) + if err != nil { + // we have no information about this and we are not a recursive dns server, so we just fail so the client can fallback to the next dns server it has configured + w.Close() + // dns.HandleFailed(w, r) + return + } + log.Printf("Not a PWD host. Looking up [%s] got [%s]\n", question, ips) + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + m.RecursionAvailable = true + for _, ip := range ips { + ipv4 := ip.To4() + if ipv4 == nil { + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN AAAA %s", question, ip.String())) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + } else { + a, err := dns.NewRR(fmt.Sprintf("%s 60 IN A %s", question, ipv4.String())) + if err != nil { + log.Fatal(err) + } + m.Answer = append(m.Answer, a) + } + } + w.WriteMsg(m) + return + + } else { + log.Printf("Not a PWD host. Got DNS without any question\n") + // we have no information about this and we are not a recursive dns server, so we just fail so the client can fallback to the next dns server it has configured + w.Close() + // dns.HandleFailed(w, r) + return + } + } +} diff --git a/handlers/exec.go b/handlers/exec.go new file mode 100644 index 0000000..eba4a93 --- /dev/null +++ b/handlers/exec.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/play-with-docker/play-with-docker/services" +) + +type execRequest struct { + Cmd []string `json:"command"` +} + +type execResponse struct { + ExitCode int `json:"status_code"` +} + +func Exec(rw http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + instanceName := vars["instanceName"] + + var er execRequest + err := json.NewDecoder(req.Body).Decode(&er) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + return + } + + code, err := services.Exec(instanceName, er.Cmd) + + if err != nil { + log.Println(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + json.NewEncoder(rw).Encode(execResponse{code}) +} diff --git a/handlers/new_session.go b/handlers/new_session.go index d4ce9b6..d8f6873 100644 --- a/handlers/new_session.go +++ b/handlers/new_session.go @@ -24,6 +24,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) { } reqDur := req.Form.Get("session-duration") + stack := req.Form.Get("stack") duration := services.GetDuration(reqDur) s, err := services.NewSession(duration) @@ -31,7 +32,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) { log.Println(err) //TODO: Return some error code } else { - + s.StackFile = stack hostname := fmt.Sprintf("%s.%s", config.PWDCName, req.Host) // If request is not a form, return sessionId in the body if req.Header.Get("X-Requested-With") == "XMLHttpRequest" { diff --git a/services/docker.go b/services/docker.go index 9bc71e2..bb64616 100644 --- a/services/docker.go +++ b/services/docker.go @@ -274,3 +274,20 @@ func CreateInstance(session *Session, dindImage string) (*Instance, error) { func DeleteContainer(id string) error { return c.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true, RemoveVolumes: true}) } + +func Exec(instanceName string, command []string) (int, error) { + e, err := c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command}) + if err != nil { + return 0, err + } + err = c.ContainerExecStart(context.Background(), e.ID, types.ExecStartCheck{}) + if err != nil { + return 0, err + } + ins, err := c.ContainerExecInspect(context.Background(), e.ID) + if err != nil { + return 0, err + } + return ins.ExitCode, nil + +} diff --git a/services/session.go b/services/session.go index 3e7a7a6..67e987d 100644 --- a/services/session.go +++ b/services/session.go @@ -54,6 +54,7 @@ type Session struct { scheduled bool `json:"-"` ticker *time.Ticker `json:"-"` PwdIpAddress string `json:"pwd_ip_address"` + StackFile string `json:"-"` } func (s *Session) Lock() { diff --git a/www/assets/button.png b/www/assets/button.png new file mode 100644 index 0000000000000000000000000000000000000000..dcea54d0fe4a6e4bd6d16138099979087a7c9ee9 GIT binary patch literal 3822 zcmV ze{faRwZ}jE+;e{=xw#PX8;pQp#PTB`0#Y3-I#$|o^jXo?=M2-#V)@+UwjSNP+6j88e}n5-Y?>LG|VhRhrxVym$J{<*tfK^!x%G9?w`YJ8sBzQBFq{n7zH2!63b- z1B4pux5{vM^{1=8`*{+L>P;J#xGSf=Rk-MqLe1qKbw-9JkTr#@Fd;~pK}N!K?%UVa z8~A+Xr>nmEL5xQA<_%TuipoR&OP3W2O&fJaheNJUP^9M_22>GR^A&B~h^f%`KfI zRc;HC$YQ!3UN0b)=|H*Wa#|WGXU8*VZ`?u_f}}Z}2q@QqQe`Sjy^oh78fT}Z$}tW6 z1)x+1tcjFz!d95Th@k^0)3k)hHnWLbLJ)J$@N)jqr~u;A4eR z5hRhqtDPp{zA&L5{~W@7))E$pfYXH#Ao?PNSO1GeN+~c5(4B)2N`E4EO`V!ny~|FW zN}FaT9=V;B(*St5y`9%W-Rbu+4815MU;tfG@_s19Q=Odzk^H%|l-atAZN0rb(AE;G z|HFbpeppxtz}DUn|K8RbE4$n0=Qf{@Rw=o@DUh}Kd}apmuJgnzfrHGiIS#utv+82&}`R&iW@oO z&+AdAPUHOLPa%W&ryBwRn%4gnfK>|?u;yFe24LA^kD}%0$LeYM`CrKS;SGTRr`JCR zfWM?<)cSaM%inq7qmK}}10f|pTDp|`uep}FQ;M0j?Opa?aV57D_$cxDDD4elN=d$E z2|*M^`T5K=3_7L60aQl?slFa}MMcWK+`wIzE$5zZT*W_ERq^K5w>dIvcE2+hq{?vQpROd zr}4_!W)@af;Bf)G@NT5!ui-ks6noAXt>rsY}FS6+0ue2^r7E&3+C2$i9Go< z#*43^&YaC~ZAiGALDr35`u&vZI#NpB`QKMr|G^#r<~g0Xw!UL&RwcRC)@U#9ucPr# z&mj9Ev9egQ(ed7U^nCJ3T;2mNH#hqV(WQY`)3`c6pZlbV+0&CuCxISd*^)}4E6MGJ z{#bTnTPtVVIye!wmbXRcokygpi6t&840^}$V{EH$0ATsdnS=uY<~l8H)Yje2v9rxM zOUqLBg^~z|#s_xxCDW=1e*E#^{0?#t_R^+6r~yY_Lue%1E>gOE(5nfgw?D2s<+Ub| zP8~EyY?)KNy~v`;IKFcu_zEz0?8Myi7Ovl~C0(e5WhKg6B2IO6vGVEPqd7EsB7OMF z%Q+JcQ|5NFva*uRon4grC$Z3uPv5X_FP@px5kec3rp-W7`SPcJN7LD6p1plFD`wAT zX?X=5e|eUo6;}^=FTcKSCHDfl)5#=<1KrjpJB}S=|H+dS&6`hMZ!cADH;b#zC$jBb zmU+DZY(ILGPM?oEfTh!C5ZS(s`5wz$*xlFwZV$TOAKzgD?=40j0L29bM4Fq2zV8&U z1PL=HGPw5T=Ro(_dIc3e2DV%DNUJ_4U9yfbqy$9N+$?^)xJ;kJ_^j8c*W* z{%zSE;11IE^+N~gbvh}&{(ABNq3gWb9pnzTn;U8t@Z8S#x#sdK(1f6& z=Uf4*wNfUdOc;mK(t3M85bc@}2GQ zwTp24;%>yWN@PYi5N~@7*0w3wh3EV#gs#W(UTSaS2Sro3xU7Q8_71Kou(r?}>*~;a zKAgot=cOV_z`K@9*$&z_&7U;r z`IaulJ8M?TG9@KVkqD>5VJh5i{%OGi0Ja}JhUWL9c|6qibX$V7crjpY^FL{7V)CLz z$^8%HUBDtw9sqkze1`CNP>F*(3@kB>Zp}@GO=5Z#9IpoxWS_)%;%SVB*P;wFZ6jH= zbzuJ2vuNL2g_z{e!U>Z+IH`yLNp=RcJx!4gF)QWr`uzaBE`)v zNqHicm&jWN^oB?u+P*X0cDHOlejHbM8A!$ME=!wuY;Clsu>o8zoc>9J_9dj^rozcA zu{Rp)_w2z{Tmq&_KM}Q&OcN!BGEe$7j#e`d^WFDg>7^DwLPKc6HqM5Bl}WXf2p>asswGBfnp?rH+CvD*{MYdCKQ(Ya-@(MJM2bIPWYdJJ!>b#^4*~sW6 zVs9|W%1M&|h!_St8|v{aU4~MU!=Y|E4FkU|NVSa(IEzb?Xhscthto-crs1`P@-Mr0 zQ`^vhf5lZJvyrT=APl;+4P3dtHV0mR0}dX7nt3?x`~jd48pC$34u67N{{rIL6=+vp zo-wn@NLoLJ=v*b0o51_s-CXZ>(;f=(=8=!_Ub-ZGCu1}qFlAl}Z8;Kp3D_Sz7rU-I zd6KYc;43MPwc8U6a*Zu$yXqS(LpyFL)ri;a2oZ-+lbEti0|Kym^nSJ{z!GwORcSJ z*t?hD-hBWucYPF#4(z98(`En)7GIK7Ug30dxqVLc>VX3ongdrw#gO;1=)*c%Hb-eL zMoF16@1>R&n>WjV9*kzHwnL$@H^lrM+vwQx1^{|-F=$$>-F=PqbneiUh$rLQP6wmop$PDcx08zbpqsp5x1N(H<%1t9gQl4r;J@-5m6`@_a2&$$* zRMI06XlsYY6R1P~2c2DrE0)9M%Mi}wgWIt_dtaEJ&qLE^P@+50HI0Zgxa#r8X`Vb8 zUro&zbMudY>`woC8jlay{>M2a(?EF{{OetC@FUcoI(Ydt2->INZWm0M0##Gt+7)oY z46xVjR8QPk3(}czm_p3~z~M8edGyV& zjnS|Q;{)5G7;6PF?(g2`x*(5Y@P9T#nJpV?oR_v-wT+?>qcHx_J$bgQWY>J$Cvv>| zA7_lhiqQW$k{I|hMTRy~wpRZ*uWaKk$K#Druu*lO87WH>-jGb_XgD%iGBVya(u5#o zOQBf@0@5%`h34pYNG8tfF#Tl9$9T(8t|P0UR4D-!(1BV^vu5B?Tjt185+=6)UtSm{ zN>QtWSSO9XA88IT;`x+g?k^Z^GehPkK*Y;(`NXB1Dg)+@HP^)2p z$XvK-!xDseOE|Q`j2%>2TFVoHG``4qx|Kpn*@l!Wj|}{TL=+cp+E@iV4=j(C2$4pR zNcJBi$(FzbzF?^MaO|z1Rv)=J#Yb%h;=)ZEms=`J4F8#}EbXK5{Mp58$X0RMnj;nE(I)07*qoM6N<$f^Rc%H2?qr literal 0 HcmV?d00001 diff --git a/www/bypass.html b/www/bypass.html index 62f1037..9a47d4a 100644 --- a/www/bypass.html +++ b/www/bypass.html @@ -11,6 +11,7 @@

We're bypassing the Captcha and redirecting you now..

+
@@ -21,4 +22,20 @@ + diff --git a/www/welcome.html b/www/welcome.html index 12117a5..b51b7fd 100644 --- a/www/welcome.html +++ b/www/welcome.html @@ -14,6 +14,7 @@
+
@@ -23,6 +24,20 @@ function iAmHuman(resp) { document.getElementById('welcomeForm').submit(); } + function getParameterByName(name, url) { + if (!url) url = window.location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + + var stack = getParameterByName('stack'); + if (stack) { + document.getElementById('stack').value = stack; + } if (document.cookie.indexOf('session_id') > -1) { document.getElementById('create').style = ""; document.getElementById('recaptcha').style = "display:none;";