Skip to content

Commit

Permalink
feat: add TLS support
Browse files Browse the repository at this point in the history
Add support for connecting to the service using TLS. Optionally,
certification validation may be disabled.

Signed-off-by: Sergei Trofimov <[email protected]>
  • Loading branch information
setrofim committed May 29, 2024
1 parent bb0fcef commit b386ec1
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 20 deletions.
58 changes: 57 additions & 1 deletion common/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ package common

import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"time"

"github.com/veraison/apiclient/auth"
Expand All @@ -22,11 +25,64 @@ type Client struct {
}

// NewClient instantiates a new Client with a fixed 5s timeout. The client will
// use the provided IAuthenticator for requests, if it is not nil
// use the provided IAuthenticator for requests, if it is not nil.
func NewClient(a auth.IAuthenticator) *Client {
return NewClientWithTransport(a, nil)
}

// NewInsecureTLSClient instantiates a new Client with a transport configured
// to accept TLS connections without verifying certs and a fixed 5s timeout.
// The client will use the provided IAuthenticator for requests, if it is not
// nil.
func NewInsecureTLSClient(a auth.IAuthenticator) *Client {
transport := http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,

Check failure on line 40 in common/client.go

View workflow job for this annotation

GitHub Actions / Lint

G402: TLS InsecureSkipVerify set true. (gosec)
},
}

return NewClientWithTransport(a, &transport)
}

// NewTLSClient instantiates a new Client with a fixed 5s timeout and transport
// configured with the system certificate pool as well as any certs provided.
// The client will use the provided IAuthenticator for requests, if it is not
// nil.
func NewTLSClient(a auth.IAuthenticator, certPaths []string) (*Client, error) {
certPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}

for _, certPath := range certPaths {
rawCert, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("could not read cert: %w", err)
}


if ok := certPool.AppendCertsFromPEM(rawCert); !ok {
return nil, fmt.Errorf("invalid cert in %s", certPath)
}
}

transport := http.Transport{
TLSClientConfig: &tls.Config{

Check failure on line 70 in common/client.go

View workflow job for this annotation

GitHub Actions / Lint

G402: TLS MinVersion too low. (gosec)
RootCAs: certPool,
},
}

return NewClientWithTransport(a, &transport), nil
}

// NewClientWithTransport instantiates a new Client with the specified transport and a fixed
// 5s timeout. The client will use the provided IAuthenticator for requests, if
// it is not nil.
func NewClientWithTransport(a auth.IAuthenticator, transport http.RoundTripper) *Client {
return &Client{
HTTPClient: http.Client{
Timeout: 5 * time.Second,
Transport: transport,
},
Auth: a,
}
Expand Down
65 changes: 53 additions & 12 deletions management/management.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ func NewService(uri string, a auth.IAuthenticator) (*Service, error) {
return &m, nil
}

// NewInsecureTLSService creates a new Service instance using the provided
// endpoint URI and an HTTPS client that does not verify certs. If the supplied
// IAuthenticator is not nil, that will be used to set the Authorization header
// in the service requests.
func NewInsecureTLSService(uri string, a auth.IAuthenticator) (*Service, error) {
m := Service{Client: common.NewInsecureTLSClient(a)}

if err := m.doSetEndpointURI(uri, true); err != nil {
return nil, err
}

return &m, nil
}

// NewTLSService creates a new Service instance using the provided endpoint URI
// and an HTTPS client configured with the specified certs (in addition to the
// system certs). If the supplied IAuthenticator is not nil, that will be used
// to set the Authorization header in the service requests.
func NewTLSService(uri string, a auth.IAuthenticator, certPaths []string) (*Service, error) {
cli, err := common.NewTLSClient(a, certPaths)
if err != nil {
return nil, err
}

m := Service{Client: cli}

if err := m.doSetEndpointURI(uri, true); err != nil {
return nil, err
}

return &m, nil
}

// SetClient sets the HTTP(s) client connection configuration
func (o *Service) SetClient(client *common.Client) error {
if client == nil {
Expand All @@ -57,18 +90,7 @@ func (o *Service) SetClient(client *common.Client) error {

// SetEndpointURI sets the URI if the Veraison services management endpoint.
func (o *Service) SetEndpointURI(uri string) error {
u, err := url.Parse(uri)
if err != nil {
return fmt.Errorf("malformed URI: %w", err)
}

if !u.IsAbs() {
return fmt.Errorf("URI is not absolute: %q", uri)
}

o.EndPointURI = u

return nil
return o.doSetEndpointURI(uri, false)
}

// CreateOPAPolicy is a wrapper around CreatePolicy that assumes the OPA media
Expand Down Expand Up @@ -273,3 +295,22 @@ func policiesFromResponse(res *http.Response) ([]*Policy, error) {

return policies, nil
}

func (o *Service) doSetEndpointURI(uri string, checkTLS bool) error {
u, err := url.Parse(uri)
if err != nil {
return fmt.Errorf("malformed URI: %w", err)
}

if !u.IsAbs() {
return fmt.Errorf("URI is not absolute: %q", uri)
}

if checkTLS && u.Scheme != "https" {
return fmt.Errorf("Expected HTTPS scheme, but got: %q", uri)

Check failure on line 310 in management/management.go

View workflow job for this annotation

GitHub Actions / Lint

error strings should not be capitalized or end with punctuation or a newline (golint)
}

o.EndPointURI = u

return nil
}
41 changes: 39 additions & 2 deletions provisioning/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type SubmitConfig struct {
SubmitURI string // URI of the /submit endpoint
DeleteSession bool // explicitly DELETE the session object after we are done
Auth auth.IAuthenticator // when set, Auth supplies the Authorization header for requests
UseTLS bool // use TLS for server connectionections
IsInsecure bool // allow insecure server connections (only matters when UseTLS is true)
CACerts []string // paths to CA certs to be used in addtion to system certs for TLS connections
}

// SetClient sets the HTTP(s) client connection configuration
Expand All @@ -58,6 +61,7 @@ func (cfg *SubmitConfig) SetSubmitURI(uri string) error {
if !u.IsAbs() {
return errors.New("uri is not absolute")
}
cfg.UseTLS = u.Scheme == "https"
cfg.SubmitURI = uri
return nil
}
Expand All @@ -75,6 +79,16 @@ func (cfg *SubmitConfig) SetAuth(a auth.IAuthenticator) {
}
}

// SetIsInsecure sets the IsInsecure parameter using the supplied val
func (cfg *SubmitConfig) SetIsInsecure(val bool) {
cfg.IsInsecure = val
}

// SetCerts sets the CACerts parameter to the specified paths
func (cfg *SubmitConfig) SetCerts(paths []string) {
cfg.CACerts = paths
}

// Run implements the endorsement submission API. If the session does not
// complete synchronously, this call will block until either the session state
// moves out of the processing state, or the MaxAttempts*PollPeriod threshold is
Expand All @@ -84,8 +98,9 @@ func (cfg SubmitConfig) Run(endorsement []byte, mediaType string) error {
return err
}

if cfg.Client == nil {
cfg.Client = common.NewClient(cfg.Auth)
// Attach the default client if the user hasn't supplied one
if err := cfg.initClient(); err != nil {
return err
}

// POST endorsement to the /submit endpoint
Expand Down Expand Up @@ -218,3 +233,25 @@ func sessionFromResponse(res *http.Response) (*SubmitSession, error) {

return &j, nil
}

func (cfg *SubmitConfig) initClient() error {
if cfg.Client != nil {
return nil // client already initialized
}

if !cfg.UseTLS {
cfg.Client = common.NewClient(cfg.Auth)
return nil
}

if cfg.IsInsecure {
cfg.Client = common.NewInsecureTLSClient(cfg.Auth)
return nil
}

var err error

cfg.Client, err = common.NewTLSClient(cfg.Auth, cfg.CACerts)

return err
}
39 changes: 39 additions & 0 deletions provisioning/provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/veraison/apiclient/auth"
"github.com/veraison/apiclient/common"
)

Expand All @@ -17,6 +18,7 @@ var (
testEndorsementMediaType = "application/corim+cbor"
testSubmitURI = "http://veraison.example/endorsement-provisioning/v1/submit"
testSessionURI = "http://veraison.example/endorsement-provisioning/v1/session/1234"
testCertPaths = []string{"/test/path1", "/test/path2"}
)

func TestSubmitConfig_check_ok(t *testing.T) {
Expand Down Expand Up @@ -373,3 +375,40 @@ func TestSubmitConfig_pollForSubmissionCompletion_success_status(t *testing.T) {
t, responseCode, []byte(sessionBody), expectedErr,
)
}

func TestSubmitConfig_initClient(t *testing.T) {
cfg := SubmitConfig{SubmitURI: testSubmitURI}
cfg.initClient()

Check failure on line 381 in provisioning/provisioning_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `cfg.initClient` is not checked (errcheck)
assert.Nil(t, cfg.Client.HTTPClient.Transport)

cfg = SubmitConfig{SubmitURI: testSubmitURI, UseTLS: true}
cfg.initClient()

Check failure on line 385 in provisioning/provisioning_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `cfg.initClient` is not checked (errcheck)
require.NotNil(t, cfg.Client.HTTPClient.Transport)
transport := cfg.Client.HTTPClient.Transport.(*http.Transport)
assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)

cfg = SubmitConfig{SubmitURI: testSubmitURI, UseTLS: true, IsInsecure: true}
cfg.initClient()

Check failure on line 391 in provisioning/provisioning_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `cfg.initClient` is not checked (errcheck)
require.NotNil(t, cfg.Client.HTTPClient.Transport)
transport = cfg.Client.HTTPClient.Transport.(*http.Transport)
assert.True(t, transport.TLSClientConfig.InsecureSkipVerify)
}

func TestSubmitConfig_setters(t *testing.T) {
cfg := SubmitConfig{SubmitURI: testSubmitURI}
require.NoError(t, cfg.initClient())

cfg.SetDeleteSession(true)
assert.True(t, cfg.DeleteSession)

a := &auth.NullAuthenticator{}
cfg.SetAuth(a)
assert.Equal(t, a, cfg.Auth)
assert.Equal(t, a, cfg.Client.Auth)

cfg.SetIsInsecure(true)
assert.True(t, cfg.IsInsecure)

cfg.SetCerts(testCertPaths)
assert.EqualValues(t, testCertPaths, cfg.CACerts)
}
47 changes: 42 additions & 5 deletions verification/challengeresponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type ChallengeResponseConfig struct {
DeleteSession bool // explicitly DELETE the session object after we are done
Wrap CmwWrap // when set, wrap the supplied evidence as a Conceptual Message Wrapper(CMW)
Auth auth.IAuthenticator // when set, Auth supplies the Authorization header for requests
UseTLS bool // use TLS for server connectionections
IsInsecure bool // allow insecure server connections (only matters when UseTLS is true)
CACerts []string // paths to CA certs to be used in addtion to system certs for TLS connections

}

// Blob wraps a base64 encoded value together with its media type
Expand Down Expand Up @@ -103,10 +107,21 @@ func (cfg *ChallengeResponseConfig) SetSessionURI(uri string) error {
if !u.IsAbs() {
return errors.New("the supplied session URI is not in absolute form")
}
cfg.UseTLS = u.Scheme == "https"
cfg.NewSessionURI = uri
return nil
}

// SetIsInsecure sets the IsInsecure parameter using the supplied val
func (cfg *ChallengeResponseConfig) SetIsInsecure(val bool) {
cfg.IsInsecure = val
}

// SetCerts sets the CACerts parameter to the specified paths
func (cfg *ChallengeResponseConfig) SetCerts(paths []string) {
cfg.CACerts = paths
}

// SetClient sets the HTTP(s) client connection configuration
func (cfg *ChallengeResponseConfig) SetClient(client *common.Client) error {
if client == nil {
Expand Down Expand Up @@ -141,14 +156,14 @@ func (cfg *ChallengeResponseConfig) SetWrap(val CmwWrap) error {

// Run implements the challenge-response protocol FSM invoking the user
// callback. On success, the received Attestation Result is returned.
func (cfg ChallengeResponseConfig) Run() ([]byte, error) {
func (cfg *ChallengeResponseConfig) Run() ([]byte, error) {
if err := cfg.check(true); err != nil {
return nil, err
}

// Attach the default client if the user hasn't supplied one
if cfg.Client == nil {
cfg.Client = common.NewClient(cfg.Auth)
if err := cfg.initClient(); err != nil {
return nil, err
}

newSessionCtx, sessionURI, err := cfg.newSession()
Expand Down Expand Up @@ -197,8 +212,8 @@ func (cfg ChallengeResponseConfig) NewSession() (*ChallengeResponseSession, stri
}

// Attach the default client if the user hasn't supplied one
if cfg.Client == nil {
cfg.Client = common.NewClient(cfg.Auth)
if err := cfg.initClient(); err != nil {
return nil, "", err
}

return cfg.newSession()
Expand Down Expand Up @@ -397,3 +412,25 @@ func (cfg ChallengeResponseConfig) pollForAttestationResult(uri string) ([]byte,

return nil, fmt.Errorf("polling attempts exhausted, session resource state still not complete")
}

func (cfg *ChallengeResponseConfig) initClient() error {
if cfg.Client != nil {
return nil // client already initialized
}

if !cfg.UseTLS {
cfg.Client = common.NewClient(cfg.Auth)
return nil
}

if cfg.IsInsecure {
cfg.Client = common.NewInsecureTLSClient(cfg.Auth)
return nil
}

var err error

cfg.Client, err = common.NewTLSClient(cfg.Auth, cfg.CACerts)

return err
}
Loading

0 comments on commit b386ec1

Please sign in to comment.