Skip to content

Commit

Permalink
Merge pull request #61 from patrickhener/villaroot-feedback
Browse files Browse the repository at this point in the history
Villaroot feedback
  • Loading branch information
patrickhener authored Apr 30, 2024
2 parents b32ef23 + 7a0bd37 commit 92dd103
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 71 deletions.
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![Version](https://img.shields.io/badge/Version-v0.3.7-green)
![Version](https://img.shields.io/badge/Version-v0.3.8-green)
[![GitHub](https://img.shields.io/github/license/patrickhener/goshs)](https://github.com/patrickhener/goshs/blob/master/LICENSE)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/patrickhener/goshs)
[![GitHub issues](https://img.shields.io/github/issues-raw/patrickhener/goshs)](https://github.com/patrickhener/goshs/issues)
Expand All @@ -22,6 +22,7 @@ goshs is a replacement for Python's `SimpleHTTPServer`. It allows uploading and
* Basic Authentication
* Transport Layer Security (HTTPS)
* self-signed
* let's encrypt
* provide own certificate
* Non persistent clipboard
* Download clipboard entries as .json file
Expand Down Expand Up @@ -83,10 +84,16 @@ Web server options:
-c, --cli Enable cli (only with auth and tls) (default: false)

TLS options:
-s, --ssl Use TLS
-ss, --self-signed Use a self-signed certificate
-sk, --server-key Path to server key
-sc, --server-cert Path to server certificate
-s, --ssl Use TLS
-ss, --self-signed Use a self-signed certificate
-sk, --server-key Path to server key
-sc, --server-cert Path to server certificate
-sl, --lets-encrypt Use Let's Encrypt as certification service
-sld, --le-domains Domain(s) to request from Let's Encrypt (comma separated list)
-sle, --le-email Email to use with Let's Encrypt
-slh, --le-http Port to use for Let's Encrypt HTTP Challenge (default: 80)
-slt, --le-tls Port to use for Let's Encrypt TLS ALPN Challenge (default: 443)
Authentication options:
-b, --basic-auth Use basic authentication (user:pass - user can be empty)
Expand All @@ -102,6 +109,7 @@ Usage examples:
Start with wevdav support: ./goshs -w
Start with different port: ./goshs -p 8080
Start with self-signed cert: ./goshs -s -ss
Start with let's encrypt: ./goshs -s -sl -sle your@mail.com -sld your.domain.com,your.seconddomain.com
Start with custom cert: ./goshs -s -sk <path to key> -sc <path to cert>
Start with basic auth: ./goshs -b secret-user:$up3r$3cur3
Start with basic auth empty user: ./goshs -b :$up3r$3cur3
Expand Down Expand Up @@ -138,6 +146,20 @@ Usage examples:

`goshs -s -ss`

*Let's encrypt*

`./goshs -s -sl -sle your@mail.com -sld your.domain.com,your.seconddomain.com`

You will have to make sure that your IP is reachable via the domain name by creating an A entry with you DNS service provider first.

Then the example command will create two files called `key` and `cert` if the request for a certificate is successful. *Please note:* for this to work let's encrypt needs to reach goshs at port 80 and 443. So you will need to start it as root. There are several options you can choose from to circumvent running goshs as root after obtaining a valid certificate:

- Drop user privileges using `-u` (preferred)
- Run it once as root until you obtain the certificate. Then stop it and rerun it using `key` and `cert` like: `./goshs -s -sk key -sc cert` as non-root user
- Use `-slh` and `-slt` to choose different challenge ports and proxy port 80 and 443 to them

After stopping goshs you can reuse the files `key` and `cert` to restart the server with a valid certificate like: `./goshs -s -sk key -sc -cert` until they are invalidated due to certificate lifetime (90 days).

*Provide own certificate*

`goshs -s -sk server.key -sc server.crt`
Expand All @@ -150,6 +172,8 @@ This mode will omit the dir listing on the web interface. Also you will not have
**Retrieve the directory listing in json format**
You can now retrieve the directory listing in *json* format. This is meant to be used with curl for example in environments where you do not have a browser on hand.



```bash
curl -s localhost:8000/?json | jq
[
Expand Down
142 changes: 142 additions & 0 deletions ca/letsencrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package ca

import (
"bufio"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"os"
"strings"

"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/patrickhener/goshs/logger"
)

// LetsEncryptUser is the struct of all information needed to rollout a lets encrypt certificate
type LetsEncryptUser struct {
Email string
Registration *registration.Resource
Key crypto.PrivateKey
HTTPPort string
TLSPort string
Domains []string
Config *lego.Config
Client *lego.Client
}

// GetEmail will return the Users Email
func (u *LetsEncryptUser) GetEmail() string {
return u.Email
}

// Get Registration will return the Registration
func (u *LetsEncryptUser) GetRegistration() *registration.Resource {
return u.Registration
}

// GetPrivateKey will return the Private Key
func (u *LetsEncryptUser) GetPrivateKey() crypto.PrivateKey {
return u.Key
}

func (u *LetsEncryptUser) RequestCertificate() ([]byte, []byte) {
// Create a user. New accounts need an email and private key to start.
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
logger.Fatalf("error generating private key for lets encrypt: %+v", err)
}

u.Key = privateKey

u.Config = lego.NewConfig(u)

// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
u.Config.Certificate.KeyType = certcrypto.RSA2048

// A client facilitates communication with the CA server.
u.Client, err = lego.NewClient(u.Config)
if err != nil {
logger.Fatalf("error retrieving client for communication with lets encrypt acme server: %+v", err)
}

err = u.Client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", u.TLSPort))
if err != nil {
logger.Fatalf("error setting tls alpn provider for lets encrypt: %+v", err)
}

err = u.Client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", u.HTTPPort))
if err != nil {
logger.Fatalf("error setting http provider for lets encrypt: %+v", err)
}

// New users will need to register
reg, err := u.Client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
logger.Fatalf("error registering new user with lets encrypt: %+v", err)
}
u.Registration = reg

request := certificate.ObtainRequest{
Domains: u.Domains,
Bundle: true,
}

certificates, err := u.Client.Certificate.Obtain(request)
if err != nil {
logger.Fatalf("error registering domain(s) with lets encrypt: %+v", err)
}

return certificates.PrivateKey, certificates.Certificate
}

func GetLECertificateAndKey(email string, domains []string, httpPort string, tlsPort string) {
// Get email if none provided
if email == "" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Provide E-Mail to use with Let's Encrypt: ")
resultEmail, err := reader.ReadString('\n')
if err != nil {
logger.Fatalf("error reading email from stdin: %+v", err)
}
email = strings.Trim(resultEmail, "\n")
}

// Get domains if none are provided
if domains[0] == "" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Provide domain(s) to request with Let's Encrypt: ")
resultDomains, err := reader.ReadString('\n')
if err != nil {
logger.Fatalf("error reading domains from stdin: %+v", err)
}
resultDomains = strings.Trim(resultDomains, "\n")
domains = strings.Split(resultDomains, ",")
}

// construct letsencrypt user object
letsencryptUser := LetsEncryptUser{
Email: email,
HTTPPort: httpPort,
TLSPort: tlsPort,
Domains: domains,
}

key, cert := letsencryptUser.RequestCertificate()

err := os.WriteFile("key", key, 0644)
if err != nil {
logger.Fatalf("error writing file 'key': %+v", err)
}

err = os.WriteFile("cert", cert, 0644)
if err != nil {
logger.Fatalf("error writing file 'cert': %+v", err)
}
}
13 changes: 11 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
module github.com/patrickhener/goshs

go 1.19
go 1.21

toolchain go1.21.8

require (
github.com/go-acme/lego/v4 v4.16.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef
github.com/sirupsen/logrus v1.8.1
github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.21.0
golang.org/x/net v0.23.0
)

require (
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/miekg/dns v1.1.58 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
)
35 changes: 30 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,23 +1,48 @@
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 3 additions & 5 deletions httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package httpserver
import (
"crypto/tls"
"fmt"
"io/ioutil"
"io"
"log"
"net"
"net/http"
Expand Down Expand Up @@ -60,7 +60,7 @@ func (fs *FileServer) Start(what string) {
// Addr: addr,
Handler: http.AllowQuerySemicolons(mux),
ReadHeaderTimeout: 10 * time.Second, // Mitigate Slow Loris Attack
ErrorLog: log.New(ioutil.Discard, "", 0),
ErrorLog: log.New(io.Discard, "", 0),
// Against good practice no timeouts here, otherwise big files would be terminated when downloaded
}

Expand Down Expand Up @@ -102,7 +102,6 @@ func (fs *FileServer) Start(what string) {
fs.dropPrivs()

logger.Panic(server.ServeTLS(listener, "", ""))
// logger.Panic(server.ListenAndServeTLS("", ""))
} else {
if fs.MyCert == "" || fs.MyKey == "" {
logger.Fatal("You need to provide server.key and server.crt if -s and not -ss")
Expand Down Expand Up @@ -131,7 +130,6 @@ func (fs *FileServer) Start(what string) {
fs.dropPrivs()

logger.Panic(server.ServeTLS(listener, "", ""))
//logger.Panic(server.ListenAndServeTLS(fs.MyCert, fs.MyKey))
}
} else {
fs.logStart(what)
Expand All @@ -140,6 +138,6 @@ func (fs *FileServer) Start(what string) {
fs.dropPrivs()

logger.Panic(server.Serve(listener))
//logger.Panic(server.ListenAndServe())
}

}
3 changes: 1 addition & 2 deletions httpserver/static/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ <h1>Upload File</h1>
<div class="input-group">
<div class="dropzone form-control" id="mydropzone">
<div class="dz-message" data-dz-message><span>Drag & Drop files here or click to
select. Submit with button on the right</span></div>
select. Files will be uploaded automatically.</span></div>
</div>
<button type="submit" id="submit-dropzone" class="btn btn-primary">+</button>
</div>

</form>
Expand Down
11 changes: 1 addition & 10 deletions httpserver/static/templates/scripts_index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,14 @@

<script>
let myDropzone = new Dropzone("div#mydropzone", {
autoProcessQueue: false,
autoProcessQueue: true,
paramName: "files",
method: "post",
url: url,
uploadMultiple: true,
parallelUploads: 100,
maxFiles: 100,
maxFilesize: 10240,

init: function () {
document.querySelector("button[type=submit]#submit-dropzone").addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
})

}
});

myDropzone.on("successmultiple", function () {
Expand Down
1 change: 1 addition & 0 deletions httpserver/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type FileServer struct {
Webroot string
SSL bool
SelfSigned bool
LetsEncrypt bool
MyKey string
MyCert string
User string
Expand Down
Loading

0 comments on commit 92dd103

Please sign in to comment.