diff --git a/README.md b/README.md index c64a0a6..52ff58e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ Setting the admin password and escrowing it post imaging: --currentpassword "" \ --cryptserver "" +If your Crypt server uses basic authentication to protect the checkin endpoint: + + sudo luks2crypt postimaging \ + --luksdevice "" \ + --currentpassword "" \ + --cryptserver "" \ + --authuser "" \ + --authpass "" + +If you omit the password, luk2crypt will prompt for one. + Development ----------- diff --git a/cmd/luks2crypt/main.go b/cmd/luks2crypt/main.go index 63813ca..eeed13e 100644 --- a/cmd/luks2crypt/main.go +++ b/cmd/luks2crypt/main.go @@ -13,6 +13,7 @@ import ( "github.com/square/luks2crypt/pkg/postimaging" + "golang.org/x/crypto/ssh/terminal" cli "gopkg.in/urfave/cli.v1" ) @@ -49,6 +50,10 @@ func run(args []string) error { cli.StringFlag{Name: "cryptendpoint, e", Usage: "The Crypt Server endpoint to use when escrowing keys", Value: "/checkin/"}, + cli.StringFlag{Name: "authuser, u", + Usage: "Basic auth username for Crypt server."}, + cli.StringFlag{Name: "authpass, P", + Usage: "Basic auth password for Crypt server. If omitted and authuser/u is specified, the password will be prompted for on the terminal."}, }, }, } @@ -66,10 +71,21 @@ func optVersion(c *cli.Context) error { func optPostImaging(c *cli.Context) error { cryptURL := "https://" + c.String("cryptserver") opts := postimaging.Opts{ - LuksDev: c.String("luksdevice"), - CurPass: c.String("currentpassword"), - Server: cryptURL, - URI: c.String("cryptendpoint"), + LuksDev: c.String("luksdevice"), + CurPass: c.String("currentpassword"), + Server: cryptURL, + URI: c.String("cryptendpoint"), + AuthUser: c.String("authuser"), + AuthPass: c.String("authpass"), + } + if (opts.AuthUser != "") && (opts.AuthPass == "") { + fmt.Printf("Password: ") + password, err := terminal.ReadPassword(0) + if err != nil { + err = fmt.Errorf("error getting basic auth password: %v", err) + return cli.NewExitError(err, 1) + } + opts.AuthPass = string(password) } err := postimaging.Run(opts) if err != nil { diff --git a/go.mod b/go.mod index 558c817..8260835 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/kr/pretty v0.1.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/sethvargo/go-diceware v0.2.0 + golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/urfave/cli.v1 v1.20.0 diff --git a/go.sum b/go.sum index 62afd4f..8ecf602 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,15 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sethvargo/go-diceware v0.2.0 h1:3QzXGqUe0UR9y1XYSz1dxGS+fKtXOxRqqKjy+cG1yTI= github.com/sethvargo/go-diceware v0.2.0/go.mod h1:II+37A5sTGAtg3zd/JqyVQ8qqAjSm/2r2X6qkVZDjyg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= diff --git a/internal/escrow/escrow.go b/internal/escrow/escrow.go index d320713..bbcbb3b 100644 --- a/internal/escrow/escrow.go +++ b/internal/escrow/escrow.go @@ -9,13 +9,14 @@ package escrow import ( "net/http" "net/url" + "strings" "github.com/gorilla/schema" ) // CryptServerInfo is used to create an object with info about the escrow server type CryptServerInfo struct { - Server, URI string + Server, URI, Username, Password string } // CryptServerData stores the data to be escrowed @@ -42,7 +43,17 @@ func (data CryptServerData) PostCryptServer(escrowServer CryptServerInfo) (*http } client := new(http.Client) - res, err := client.PostForm(cryptServer, form) + req, err := http.NewRequest("POST", cryptServer, strings.NewReader(form.Encode())) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + if (escrowServer.Username != "") && (escrowServer.Password != "") { + req.SetBasicAuth(escrowServer.Username, escrowServer.Password) + } + + res, err := client.Do(req) if err != nil { return nil, err } diff --git a/internal/escrow/escrow_test.go b/internal/escrow/escrow_test.go index 7daa2b3..1f7c915 100644 --- a/internal/escrow/escrow_test.go +++ b/internal/escrow/escrow_test.go @@ -20,53 +20,68 @@ func TestPostCryptServer(t *testing.T) { Username: "tester", } - mockCryptServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) + basicauth := []string{"DarthHelmet", "12345"} + tests := []struct { + name string + username string + password string + }{ + {name: "No auth escrow"}, + {name: "Basic auth escrow", username: basicauth[0], password: basicauth[1]}, + } - if r.Method != "POST" { - t.Errorf("expected 'POST' request, got '%s'", r.Method) - } + for _, tc := range tests { + mockCryptServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) - if r.URL.EscapedPath() != "/checkin/" { - t.Errorf("expected request to '/checkin', got '%s'", r.URL.EscapedPath()) - } + if r.Method != "POST" { + t.Errorf("expected 'POST' request, got '%s'", r.Method) + } - // cryptserver expects the following form data recovery_password, serial, - // macname, username - // see: https://github.com/grahamgilbert/Crypt-Server/blob/master/server/views.py#L442 - r.ParseForm() - actual := r.Form.Get("recovery_password") - if actual != expected.Pass { - t.Errorf("expected 'recovery_password=%v' got %v", expected.Pass, actual) - } + if r.URL.EscapedPath() != "/checkin/" { + t.Errorf("expected request to '/checkin', got '%s'", r.URL.EscapedPath()) + } - actual = r.Form.Get("serial") - if actual != expected.Serialnum { - t.Errorf("expected 'serial=%v' got %v", expected.Serialnum, actual) - } + authusername, authpassword, _ := r.BasicAuth() + if authusername != tc.username || authpassword != tc.password { + t.Errorf("expected '%s:%s' got '%s:%s'", tc.username, tc.password, authusername, authpassword) + } - actual = r.Form.Get("macname") - if actual != expected.Hostname { - t.Errorf("expected 'macname=%v' got %v", expected.Hostname, actual) - } + // cryptserver expects the following form data recovery_password, serial, + // macname, username + // see: https://github.com/grahamgilbert/Crypt-Server/blob/master/server/views.py#L442 + r.ParseForm() + actual := r.Form.Get("recovery_password") + if actual != expected.Pass { + t.Errorf("expected 'recovery_password=%v' got %v", expected.Pass, actual) + } - actual = r.Form.Get("username") - if actual != expected.Username { - t.Errorf("expected 'username=%v' got %v", expected.Username, actual) - } - })) - defer mockCryptServer.Close() + actual = r.Form.Get("serial") + if actual != expected.Serialnum { + t.Errorf("expected 'serial=%v' got %v", expected.Serialnum, actual) + } - endpoint := CryptServerInfo{ - Server: mockCryptServer.URL, - URI: "/checkin/", - } + actual = r.Form.Get("macname") + if actual != expected.Hostname { + t.Errorf("expected 'macname=%v' got %v", expected.Hostname, actual) + } - resp, err := expected.PostCryptServer(endpoint) - if err != nil { - t.Errorf("errored posting escrow data to mock cryptserver with %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Errorf("errored posting escrow data to mock cryptserver expected 200 got %v", resp.StatusCode) + actual = r.Form.Get("username") + if actual != expected.Username { + t.Errorf("expected 'username=%v' got %v", expected.Username, actual) + } + })) + defer mockCryptServer.Close() + + endpoint := CryptServerInfo{Server: mockCryptServer.URL, URI: "/checkin/", Username: tc.username, Password: tc.password} + t.Run(tc.name, func(t *testing.T) { + resp, err := expected.PostCryptServer(endpoint) + if err != nil { + t.Errorf("errored posting escrow data to mock cryptserver with %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("errored posting escrow data to mock cryptserver expected 200 got %v", resp.StatusCode) + } + }) } } diff --git a/pkg/postimaging/postimaging.go b/pkg/postimaging/postimaging.go index fdd1aaa..275043b 100644 --- a/pkg/postimaging/postimaging.go +++ b/pkg/postimaging/postimaging.go @@ -20,14 +20,16 @@ import ( // Opts is used to store the options needed for postimaging functions type Opts struct { - LuksDev, CurPass, Server, URI string + LuksDev, CurPass, Server, URI, AuthUser, AuthPass string } // Run post imaging password creation, set, and escrow. Returns an error func Run(opts Opts) error { cryptServerInfo := escrow.CryptServerInfo{ - Server: opts.Server, - URI: opts.URI, + Server: opts.Server, + URI: opts.URI, + Username: opts.AuthUser, + Password: opts.AuthPass, } cryptServerData := escrow.CryptServerData{}