Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BundleSign and BundleAuthSign methods to client Remote #1219

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 73 additions & 17 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ type server struct {
// are used by the remote.
type Remote interface {
AuthSign(req, id []byte, provider auth.Provider) ([]byte, error)
BundleAuthSign(req, id []byte, provider auth.Provider) ([]byte, []byte, error)
Sign(jsonData []byte) ([]byte, error)
BundleSign(jsonData []byte) ([]byte, []byte, error)
Info(jsonData []byte) (*info.Resp, error)
Hosts() []string
SetReqModifier(func(*http.Request, []byte))
Expand Down Expand Up @@ -190,26 +192,38 @@ func (srv *server) post(url string, jsonData []byte) (*api.Response, error) {
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *server) AuthSign(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.authReq(req, id, provider, "sign")
_, cert, err := srv.authReq(req, id, provider, "sign", false)
return cert, err
}

// BundleAuthSign fills out an authenticated signing request to the server,
// receiving a signed certificate in an "optimal" bundle, the root CA
// (if provided by the API) or an error in response.
// It takes the serialized JSON request to send which is required to
// have the bundle parameter set to true, remote address and authentication
// provider.
func (srv *server) BundleAuthSign(req, id []byte, provider auth.Provider) ([]byte, []byte, error) {
return srv.authReq(req, id, provider, "sign", true)
}

// AuthInfo fills out an authenticated info request to the server,
// receiving a certificate or error in response.
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *server) AuthInfo(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.authReq(req, id, provider, "info")
_, cert, err := srv.authReq(req, id, provider, "info", false)
return cert, err
}

// authReq is the common logic for AuthSign and AuthInfo -- perform the given
// request, and return the resultant certificate.
// The target is either 'sign' or 'info'.
func (srv *server) authReq(req, ID []byte, provider auth.Provider, target string) ([]byte, error) {
func (srv *server) authReq(req, ID []byte, provider auth.Provider, target string, returnBundle bool) ([]byte, []byte, error) {
url := srv.getURL("auth" + target)

token, err := provider.Token(req)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.AuthenticationFailure, err)
return nil, nil, errors.Wrap(errors.APIClientError, errors.AuthenticationFailure, err)
}

aReq := &auth.AuthenticatedRequest{
Expand All @@ -221,32 +235,54 @@ func (srv *server) authReq(req, ID []byte, provider auth.Provider, target string

jsonData, err := json.Marshal(aReq)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
return nil, nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
}

response, err := srv.post(url, jsonData)
if err != nil {
return nil, err
return nil, nil, err
}

result, ok := response.Result.(map[string]interface{})
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
return nil, nil, errors.New(errors.APIClientError, errors.JSONError)
}

cert, ok := result["certificate"].(string)
var ca, cert string
if returnBundle {
bundle, okBundle := result["bundle"].(map[string]interface{})
if !okBundle {
return nil, nil, errors.New(errors.APIClientError, errors.JSONError)
}
cert, ok = bundle["bundle"].(string)
// The API docs are not clear if root is always returned.
// So make sure to not panic and return the bundle even if root is not returned.
ca, _ = bundle["root"].(string)
} else {
cert, ok = result["certificate"].(string)
}
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
return nil, nil, errors.New(errors.APIClientError, errors.JSONError)
}

return []byte(cert), nil
return []byte(ca), []byte(cert), nil
}

// Sign sends a signature request to the remote CFSSL server,
// receiving a signed certificate or an error in response.
// It takes the serialized JSON request to send.
func (srv *server) Sign(jsonData []byte) ([]byte, error) {
return srv.request(jsonData, "sign")
_, cert, err := srv.request(jsonData, "sign", false)
return cert, err
}

// BundleSign sends a signature request to the remote CFSSL server,
// receiving a signed certificate in an "optimal" bundle, the root CA
// (if provided by the API) or an error in response.
// It takes the serialized JSON request to send which is required to
// have the bundle parameter set to true.
func (srv *server) BundleSign(jsonData []byte) ([]byte, []byte, error) {
return srv.request(jsonData, "sign", true)
}

// Info sends an info request to the remote CFSSL server, receiving a
Expand Down Expand Up @@ -294,18 +330,33 @@ func (srv *server) getResultMap(jsonData []byte, target string) (result map[stri
}

// request performs the common logic for Sign and Info, performing the actual
// request and returning the resultant certificate.
func (srv *server) request(jsonData []byte, target string) ([]byte, error) {
// request and returning the resultant certificate or bundle.
func (srv *server) request(jsonData []byte, target string, returnBundle bool) ([]byte, []byte, error) {
result, err := srv.getResultMap(jsonData, target)
if err != nil {
return nil, err
return nil, nil, err
}

var ca, cert, key string
if returnBundle {
key = "bundle"
bundle, okBundle := result[key].(map[string]interface{})
if okBundle {
cert = bundle[key].(string)
// The API docs are not clear if root is always returned.
// So make sure to not panic and return the bundle even if root is not returned.
ca, _ = bundle["root"].(string)
}
} else {
key = "certificate"
cert = result[key].(string)
}
cert := result["certificate"].(string)

if cert != "" {
return []byte(cert), nil
return []byte(ca), []byte(cert), nil
}

return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New("response doesn't contain certificate."))
return nil, nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New(fmt.Sprintf("response doesn't contain %s.", key)))
}

// AuthRemote acts as a Remote with a default Provider for AuthSign.
Expand All @@ -329,6 +380,11 @@ func (ar *AuthRemote) Sign(req []byte) ([]byte, error) {
return ar.AuthSign(req, nil, ar.provider)
}

// BundleSign is overloaded to perform an BundleAuthSign request using the default auth provider.
func (ar *AuthRemote) BundleSign(req []byte) ([]byte, []byte, error) {
return ar.BundleAuthSign(req, nil, ar.provider)
}

// normalizeURL checks for http/https protocol, appends "http" as default protocol if not defined in url
func normalizeURL(addr string) (*url.URL, error) {
addr = strings.TrimSpace(addr)
Expand Down
25 changes: 22 additions & 3 deletions api/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package client

import (
"crypto/tls"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/helpers"
"net"
"strings"
"testing"

"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/helpers"
)

var (
Expand Down Expand Up @@ -60,15 +61,25 @@ func TestInvalidPort(t *testing.T) {
}

func TestAuthSign(t *testing.T) {
s := NewServer(".X")
testProvider, _ = auth.New(testKey, nil)
s := NewAuthServer(".X", nil, testProvider)
testRequest := []byte(`testing 1 2 3`)
as, err := s.AuthSign(testRequest, testAD, testProvider)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}

func TestBundleAuthSign(t *testing.T) {
testProvider, _ = auth.New(testKey, nil)
s := NewAuthServer(".X", nil, testProvider)
testRequest := []byte(`testing 1 2 3`)
_, as, err := s.BundleAuthSign(testRequest, testAD, testProvider)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}

func TestDefaultAuthSign(t *testing.T) {
testProvider, _ = auth.New(testKey, nil)
s := NewAuthServer(".X", nil, testProvider)
Expand All @@ -87,6 +98,14 @@ func TestSign(t *testing.T) {
}
}

func TestBundleSign(t *testing.T) {
s := NewServer(".X")
_, sign, err := s.BundleSign([]byte{5, 5, 5, 5})
if sign != nil || err == nil {
t.Fatalf("expected error with sign function")
}
}

func TestNewMutualTLSServer(t *testing.T) {
cert, _ := helpers.LoadClientCertificate("../../helpers/testdata/ca.pem", "../../helpers/testdata/ca_key.pem")
s := NewServerTLS("https://nohost:8888", helpers.CreateTLSConfig(nil, cert))
Expand Down
22 changes: 22 additions & 0 deletions api/client/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ func (g *orderedListGroup) AuthSign(req, id []byte, provider auth.Provider) (res
return nil, err
}

func (g *orderedListGroup) BundleAuthSign(req, id []byte, provider auth.Provider) (ca []byte, cert []byte, err error) {
for i := range g.remotes {
ca, cert, err = g.remotes[i].BundleAuthSign(req, id, provider)
if err == nil {
return ca, cert, nil
}
}

return nil, nil, err
}

func (g *orderedListGroup) Sign(jsonData []byte) (resp []byte, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Sign(jsonData)
Expand All @@ -115,6 +126,17 @@ func (g *orderedListGroup) Sign(jsonData []byte) (resp []byte, err error) {
return nil, err
}

func (g *orderedListGroup) BundleSign(jsonData []byte) (ca []byte, cert []byte, err error) {
for i := range g.remotes {
ca, cert, err = g.remotes[i].BundleSign(jsonData)
if err == nil {
return ca, cert, nil
}
}

return nil, nil, err
}

func (g *orderedListGroup) Info(jsonData []byte) (resp *info.Resp, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Info(jsonData)
Expand Down