Skip to content

Commit

Permalink
Move CTL logging logic over to CTL package (#353)
Browse files Browse the repository at this point in the history
* Refacter CT log client to use interface

Signed-off-by: Nathan Smith <nathan@nfsmith.ca>

* Add logging middleware for CTL client

Signed-off-by: Nathan Smith <nathan@nfsmith.ca>

* Log in CTL client not API handler

Removes CTL related logging from the API handler and pushes it
over to the CTL client.

Signed-off-by: Nathan Smith <nathan@nfsmith.ca>
  • Loading branch information
nsmith5 authored Feb 4, 2022
1 parent 3c3a008 commit 3f1e17e
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 24 deletions.
3 changes: 2 additions & 1 deletion cmd/app/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ func runServeCmd(cmd *cobra.Command, args []string) {
host, port := viper.GetString("host"), viper.GetString("port")
log.Logger.Infof("%s:%s", host, port)

var ctClient *ctl.Client
var ctClient ctl.Client
if logURL := viper.GetString("ct-log-url"); logURL != "" {
ctClient = ctl.New(logURL)
ctClient = ctl.WithLogging(ctClient, log.Logger)
}

var handler http.Handler
Expand Down
7 changes: 2 additions & 5 deletions pkg/api/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ const (
)

type api struct {
ct *ctl.Client
ct ctl.Client
ca certauth.CertificateAuthority

*http.ServeMux
}

// New creates a new http.Handler for serving the Fulcio API.
func New(ct *ctl.Client, ca certauth.CertificateAuthority) http.Handler {
func New(ct ctl.Client, ca certauth.CertificateAuthority) http.Handler {
var a api
a.ServeMux = http.NewServeMux()
a.HandleFunc(signingCertPath, a.signingCert)
Expand Down Expand Up @@ -175,7 +175,6 @@ func (a *api) signingCert(w http.ResponseWriter, req *http.Request) {
}

// Submit to CTL
logger.Info("Submitting CTL inclusion for OIDC grant: ", subject.Value)
if a.ct != nil {
sct, err := a.ct.AddChain(csc)
if err != nil {
Expand All @@ -187,8 +186,6 @@ func (a *api) signingCert(w http.ResponseWriter, req *http.Request) {
handleFulcioAPIError(w, req, http.StatusInternalServerError, err, failedToMarshalSCT)
return
}
logger.Info("CTL Submission Signature Received: ", sct.Signature)
logger.Info("CTL Submission ID Received: ", sct.ID)
} else {
logger.Info("Skipping CT log upload.")
}
Expand Down
21 changes: 4 additions & 17 deletions pkg/ctl/ctl.go → pkg/ctl/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import (

const addChainPath = "ct/v1/add-chain"

type Client struct {
type client struct {
c *http.Client
url string
}

func New(url string) *Client {
func New(url string) Client {
c := &http.Client{Timeout: 30 * time.Second}
return &Client{
return &client{
c: c,
url: url,
}
Expand All @@ -53,20 +53,7 @@ type CertChainResponse struct {
Signature string `json:"signature"`
}

type ErrorResponse struct {
StatusCode int `json:"statusCode"`
ErrorCode string `json:"errorCode"`
Message string `json:"message"`
}

func (err *ErrorResponse) Error() string {
if err.ErrorCode == "" {
return fmt.Sprintf("%d CT API error: %s", err.StatusCode, err.Message)
}
return fmt.Sprintf("%d (%s) CT API error: %s", err.StatusCode, err.ErrorCode, err.Message)
}

func (c *Client) AddChain(csc *ca.CodeSigningCertificate) (*CertChainResponse, error) {
func (c *client) AddChain(csc *ca.CodeSigningCertificate) (*CertChainResponse, error) {
chainjson := &certChain{Chain: []string{
base64.StdEncoding.EncodeToString(csc.FinalCertificate.Raw),
}}
Expand Down
2 changes: 1 addition & 1 deletion pkg/ctl/ctl_test.go → pkg/ctl/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func Test_AddChain(t *testing.T) {
}))
defer server.Close()

api := Client{server.Client(), server.URL}
api := New(server.URL)
csc, _ := ca.CreateCSCFromPEM(nil, rootCert, clientCert)
body, err := api.AddChain(csc)
assert.NoError(t, err)
Expand Down
44 changes: 44 additions & 0 deletions pkg/ctl/ctl_logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package ctl

import (
"github.com/sigstore/fulcio/pkg/ca"
"go.uber.org/zap"
)

type ctlLoggingClient struct {
next Client
logger *zap.SugaredLogger
}

func (lc *ctlLoggingClient) AddChain(csc *ca.CodeSigningCertificate) (*CertChainResponse, error) {
lc.logger.Info("Submitting CTL inclusion for subject", csc.Subject.Value)
resp, err := lc.next.AddChain(csc)
if err != nil {
lc.logger.Error("Failed to submit certificate to CTL with error", err)
return nil, err
}
lc.logger.Info("CTL Submission Signature Received: ", resp.Signature)
lc.logger.Info("CTL Submission ID Received: ", resp.ID)
return resp, nil
}

// WithLogging adds logging (in the writing helpful information to console
// sense) to a certificate transparenct log client
func WithLogging(next Client, logger *zap.SugaredLogger) Client {
return &ctlLoggingClient{next, logger}
}
78 changes: 78 additions & 0 deletions pkg/ctl/ctl_logging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ctl

import (
"errors"
"regexp"
"testing"

"github.com/sigstore/fulcio/pkg/ca"
"github.com/sigstore/fulcio/pkg/challenges"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)

type clientFunc func(csc *ca.CodeSigningCertificate) (*CertChainResponse, error)

func (f clientFunc) AddChain(csc *ca.CodeSigningCertificate) (*CertChainResponse, error) {
return f(csc)
}

func TestWithLogging(t *testing.T) {
tests := map[string]struct {
Client Client
ExpectedOutput *regexp.Regexp
}{
"Error in client should be logged": {
clientFunc(func(*ca.CodeSigningCertificate) (*CertChainResponse, error) {
return nil, errors.New(`ctl: testing error`)
}),
regexp.MustCompile(`ctl: testing error`),
},
"Success in client should log information": {
clientFunc(func(*ca.CodeSigningCertificate) (*CertChainResponse, error) {
return &CertChainResponse{}, nil
}),
regexp.MustCompile(`CTL Submission ID Received:`),
},
}

for test, data := range tests {
t.Run(test, func(t *testing.T) {
// Given
observedZapCore, observedLogs := observer.New(zap.InfoLevel)
observedLogger := zap.New(observedZapCore)
client := WithLogging(data.Client, observedLogger.Sugar())

csc := ca.CodeSigningCertificate{
Subject: &challenges.ChallengeResult{},
}

// When
_, _ = client.AddChain(&csc)

// Then
for _, entry := range observedLogs.All() {
if data.ExpectedOutput.MatchString(entry.Message) {
// We received expected output so the test passes
return
}
}
// If we got here we didn't match the expected output so test fails
t.Error("Didn't find expected output in logs")
})
}
}
31 changes: 31 additions & 0 deletions pkg/ctl/errorresponse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package ctl

import "fmt"

type ErrorResponse struct {
StatusCode int `json:"statusCode"`
ErrorCode string `json:"errorCode"`
Message string `json:"message"`
}

func (err *ErrorResponse) Error() string {
if err.ErrorCode == "" {
return fmt.Sprintf("%d CT API error: %s", err.StatusCode, err.Message)
}
return fmt.Sprintf("%d (%s) CT API error: %s", err.StatusCode, err.ErrorCode, err.Message)
}
22 changes: 22 additions & 0 deletions pkg/ctl/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package ctl

import "github.com/sigstore/fulcio/pkg/ca"

type Client interface {
AddChain(csc *ca.CodeSigningCertificate) (*CertChainResponse, error)
}

0 comments on commit 3f1e17e

Please sign in to comment.