Skip to content

Commit c451127

Browse files
authored
Add logging to certpoolwatcher and client (#1684)
Logging now indicates what certificate (by file and X.509 name) is being watched When an unverified certificate error is returned to the client, log the cert Signed-off-by: Todd Short <[email protected]>
1 parent 9864ba7 commit c451127

File tree

9 files changed

+204
-8
lines changed

9 files changed

+204
-8
lines changed

Diff for: catalogd/cmd/catalogd/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"github.com/containers/image/v5/types"
3131
"github.com/go-logr/logr"
32+
"github.com/sirupsen/logrus"
3233
"github.com/spf13/pflag"
3334
corev1 "k8s.io/api/core/v1"
3435
"k8s.io/apimachinery/pkg/fields"
@@ -120,6 +121,9 @@ func main() {
120121
flag.StringVar(&globalPullSecret, "global-pull-secret", "", "The <namespace>/<name> of the global pull secret that is going to be used to pull bundle images.")
121122

122123
klog.InitFlags(flag.CommandLine)
124+
if klog.V(4).Enabled() {
125+
logrus.SetLevel(logrus.DebugLevel)
126+
}
123127

124128
// Combine both flagsets and parse them
125129
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

Diff for: catalogd/internal/source/containers_image.go

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2424

2525
catalogdv1 "github.com/operator-framework/operator-controller/catalogd/api/v1"
26+
"github.com/operator-framework/operator-controller/internal/httputil"
2627
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
2728
imageutil "github.com/operator-framework/operator-controller/internal/util/image"
2829
)
@@ -54,6 +55,18 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, catalog *catalogdv
5455
if err != nil {
5556
return nil, err
5657
}
58+
59+
res, err := i.unpack(ctx, catalog, srcCtx, l)
60+
if err != nil {
61+
// Log any CertificateVerificationErrors, and log Docker Certificates if necessary
62+
if httputil.LogCertificateVerificationError(err, l) {
63+
httputil.LogDockerCertificates(srcCtx.DockerCertPath, l)
64+
}
65+
}
66+
return res, err
67+
}
68+
69+
func (i *ContainersImageRegistry) unpack(ctx context.Context, catalog *catalogdv1.ClusterCatalog, srcCtx *types.SystemContext, l logr.Logger) (*Result, error) {
5770
//////////////////////////////////////////////////////
5871
//
5972
// Resolve a canonical reference for the image.

Diff for: cmd/operator-controller/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"github.com/containers/image/v5/types"
3131
"github.com/go-logr/logr"
32+
"github.com/sirupsen/logrus"
3233
"github.com/spf13/pflag"
3334
corev1 "k8s.io/api/core/v1"
3435
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
@@ -121,6 +122,9 @@ func main() {
121122
flag.StringVar(&globalPullSecret, "global-pull-secret", "", "The <namespace>/<name> of the global pull secret that is going to be used to pull bundle images.")
122123

123124
klog.InitFlags(flag.CommandLine)
125+
if klog.V(4).Enabled() {
126+
logrus.SetLevel(logrus.DebugLevel)
127+
}
124128

125129
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
126130
features.OperatorControllerFeatureGate.AddFlag(pflag.CommandLine)

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/operator-framework/helm-operator-plugins v0.8.0
2323
github.com/operator-framework/operator-registry v1.50.0
2424
github.com/prometheus/client_golang v1.20.5
25+
github.com/sirupsen/logrus v1.9.3
2526
github.com/spf13/pflag v1.0.6
2627
github.com/stretchr/testify v1.10.0
2728
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
@@ -195,7 +196,6 @@ require (
195196
github.com/sigstore/fulcio v1.6.4 // indirect
196197
github.com/sigstore/rekor v1.3.6 // indirect
197198
github.com/sigstore/sigstore v1.8.9 // indirect
198-
github.com/sirupsen/logrus v1.9.3 // indirect
199199
github.com/spf13/cast v1.7.0 // indirect
200200
github.com/spf13/cobra v1.8.1 // indirect
201201
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect

Diff for: internal/catalogmetadata/client/client.go

+3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111

1212
"k8s.io/apimachinery/pkg/api/meta"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
ctrl "sigs.k8s.io/controller-runtime"
1415

1516
"github.com/operator-framework/operator-registry/alpha/declcfg"
1617

1718
catalogd "github.com/operator-framework/operator-controller/catalogd/api/v1"
19+
"github.com/operator-framework/operator-controller/internal/httputil"
1820
)
1921

2022
const (
@@ -133,6 +135,7 @@ func (c *Client) doRequest(ctx context.Context, catalog *catalogd.ClusterCatalog
133135

134136
resp, err := client.Do(req)
135137
if err != nil {
138+
_ = httputil.LogCertificateVerificationError(err, ctrl.Log.WithName("catalog-client"))
136139
return nil, fmt.Errorf("error performing request: %v", err)
137140
}
138141

Diff for: internal/httputil/certlog.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package httputil
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"errors"
8+
"fmt"
9+
"os"
10+
"path/filepath"
11+
12+
"github.com/go-logr/logr"
13+
)
14+
15+
const (
16+
defaultLogLevel = 4
17+
)
18+
19+
// Log the certificates that would be used for docker pull operations
20+
// Assumes a /etc/docker/certs.d like path, where the directory contains
21+
// <hostname>:<port> directories in which a CA certificate (generally
22+
// named "ca.crt") is located.
23+
func LogDockerCertificates(path string, log logr.Logger) {
24+
// These are the default paths that containers/images looks at for host:port certs
25+
// See containers/images: docker/docker_client.go
26+
paths := []string{"/etc/docker/certs.d", "/etc/containers/certs.d"}
27+
if path != "" {
28+
paths = []string{path}
29+
}
30+
for _, path := range paths {
31+
fi, err := os.Stat(path)
32+
if err != nil {
33+
log.Error(err, "statting directory", "directory", path)
34+
continue
35+
}
36+
if !fi.IsDir() {
37+
log.V(defaultLogLevel+1).Info("not a directory", "directory", path)
38+
continue
39+
}
40+
dirEntries, err := os.ReadDir(path)
41+
if err != nil {
42+
log.Error(err, "reading directory", "directory", path)
43+
continue
44+
}
45+
for _, e := range dirEntries {
46+
hostPath := filepath.Join(path, e.Name())
47+
fi, err := os.Stat(hostPath)
48+
if err != nil {
49+
log.Error(err, "dumping certs", "path", hostPath)
50+
continue
51+
}
52+
if !fi.IsDir() {
53+
log.V(defaultLogLevel+1).Info("ignoring non-directory", "path", hostPath)
54+
continue
55+
}
56+
logPath(hostPath, "dump docker certs", log)
57+
}
58+
}
59+
}
60+
61+
// This function unwraps the given error to find an CertificateVerificationError.
62+
// It then logs the list of certificates found in the unwrapped error
63+
// Returns:
64+
// * true if a CertificateVerificationError is found
65+
// * false if no CertificateVerificationError is found
66+
func LogCertificateVerificationError(err error, log logr.Logger) bool {
67+
for err != nil {
68+
var cvErr *tls.CertificateVerificationError
69+
if errors.As(err, &cvErr) {
70+
n := 1
71+
for _, cert := range cvErr.UnverifiedCertificates {
72+
log.Error(err, "unverified cert", "n", n, "subject", cert.Subject, "issuer", cert.Issuer, "DNSNames", cert.DNSNames, "serial", cert.SerialNumber)
73+
n = n + 1
74+
}
75+
return true
76+
}
77+
err = errors.Unwrap(err)
78+
}
79+
return false
80+
}
81+
82+
func logPath(path, action string, log logr.Logger) {
83+
fi, err := os.Stat(path)
84+
if err != nil {
85+
log.Error(err, "error in os.Stat()", "path", path)
86+
return
87+
}
88+
if !fi.IsDir() {
89+
logFile(path, "", fmt.Sprintf("%s file", action), log)
90+
return
91+
}
92+
action = fmt.Sprintf("%s directory", action)
93+
dirEntries, err := os.ReadDir(path)
94+
if err != nil {
95+
log.Error(err, "error in os.ReadDir()", "path", path)
96+
return
97+
}
98+
for _, e := range dirEntries {
99+
file := filepath.Join(path, e.Name())
100+
fi, err := os.Stat(file)
101+
if err != nil {
102+
log.Error(err, "error in os.Stat()", "file", file)
103+
continue
104+
}
105+
if fi.IsDir() {
106+
log.V(defaultLogLevel+1).Info("ignoring subdirectory", "directory", file)
107+
continue
108+
}
109+
logFile(e.Name(), path, action, log)
110+
}
111+
}
112+
113+
func logFile(filename, path, action string, log logr.Logger) {
114+
filepath := filepath.Join(path, filename)
115+
_, err := os.Stat(filepath)
116+
if err != nil {
117+
log.Error(err, "statting file", "file", filepath)
118+
return
119+
}
120+
data, err := os.ReadFile(filepath)
121+
if err != nil {
122+
log.Error(err, "error in os.ReadFile()", "file", filepath)
123+
return
124+
}
125+
logPem(data, filename, path, action, log)
126+
}
127+
128+
func logPem(data []byte, filename, path, action string, log logr.Logger) {
129+
for len(data) > 0 {
130+
var block *pem.Block
131+
block, data = pem.Decode(data)
132+
if block == nil {
133+
log.Info("error: no block returned from pem.Decode()", "file", filename)
134+
return
135+
}
136+
crt, err := x509.ParseCertificate(block.Bytes)
137+
if err != nil {
138+
log.Error(err, "error in x509.ParseCertificate()", "file", filename)
139+
return
140+
}
141+
142+
args := []any{}
143+
if path != "" {
144+
args = append(args, "directory", path)
145+
}
146+
// Find an appopriate certificate identifier
147+
args = append(args, "file", filename)
148+
if s := crt.Subject.String(); s != "" {
149+
args = append(args, "subject", s)
150+
} else if crt.DNSNames != nil {
151+
args = append(args, "DNSNames", crt.DNSNames)
152+
} else if s := crt.SerialNumber.String(); s != "" {
153+
args = append(args, "serial", s)
154+
}
155+
log.V(defaultLogLevel).Info(action, args...)
156+
}
157+
}

Diff for: internal/httputil/certpoolwatcher.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ func NewCertPoolWatcher(caDir string, log logr.Logger) (*CertPoolWatcher, error)
5050
// If the SSL_CERT_DIR or SSL_CERT_FILE environment variables are
5151
// specified, this means that we have some control over the system root
5252
// location, thus they may change, thus we should watch those locations.
53-
watchPaths := strings.Split(os.Getenv("SSL_CERT_DIR"), ":")
54-
watchPaths = append(watchPaths, caDir, os.Getenv("SSL_CERT_FILE"))
53+
sslCertDir := os.Getenv("SSL_CERT_DIR")
54+
sslCertFile := os.Getenv("SSL_CERT_FILE")
55+
log.V(defaultLogLevel).Info("SSL environment", "SSL_CERT_DIR", sslCertDir, "SSL_CERT_FILE", sslCertFile)
56+
57+
watchPaths := strings.Split(sslCertDir, ":")
58+
watchPaths = append(watchPaths, caDir, sslCertFile)
5559
watchPaths = slices.DeleteFunc(watchPaths, func(p string) bool {
5660
if p == "" {
5761
return true
@@ -66,6 +70,7 @@ func NewCertPoolWatcher(caDir string, log logr.Logger) (*CertPoolWatcher, error)
6670
if err := watcher.Add(p); err != nil {
6771
return nil, err
6872
}
73+
logPath(p, "watching certificate", log)
6974
}
7075

7176
cpw := &CertPoolWatcher{

Diff for: internal/httputil/certutil.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8-
"time"
98

109
"github.com/go-logr/logr"
1110
)
@@ -24,7 +23,6 @@ func NewCertPool(caDir string, log logr.Logger) (*x509.CertPool, error) {
2423
return nil, err
2524
}
2625
count := 0
27-
firstExpiration := time.Time{}
2826

2927
for _, e := range dirEntries {
3028
file := filepath.Join(caDir, e.Name())
@@ -34,10 +32,10 @@ func NewCertPool(caDir string, log logr.Logger) (*x509.CertPool, error) {
3432
return nil, err
3533
}
3634
if fi.IsDir() {
37-
log.Info("skip directory", "name", e.Name())
35+
log.V(defaultLogLevel+1).Info("skip directory", "name", e.Name())
3836
continue
3937
}
40-
log.Info("load certificate", "name", e.Name())
38+
log.V(defaultLogLevel+1).Info("load certificate", "name", e.Name())
4139
data, err := os.ReadFile(file)
4240
if err != nil {
4341
return nil, fmt.Errorf("error reading cert file %q: %w", file, err)
@@ -46,13 +44,13 @@ func NewCertPool(caDir string, log logr.Logger) (*x509.CertPool, error) {
4644
if caCertPool.AppendCertsFromPEM(data) {
4745
count++
4846
}
47+
logPem(data, e.Name(), caDir, "loading certificate file", log)
4948
}
5049

5150
// Found no certs!
5251
if count == 0 {
5352
return nil, fmt.Errorf("no certificates found in %q", caDir)
5453
}
5554

56-
log.Info("first expiration", "time", firstExpiration.Format(time.RFC3339))
5755
return caCertPool, nil
5856
}

Diff for: internal/rukpak/source/containers_image.go

+12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/log"
2121
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2222

23+
"github.com/operator-framework/operator-controller/internal/httputil"
2324
fsutil "github.com/operator-framework/operator-controller/internal/util/fs"
2425
imageutil "github.com/operator-framework/operator-controller/internal/util/image"
2526
)
@@ -50,6 +51,17 @@ func (i *ContainersImageRegistry) Unpack(ctx context.Context, bundle *BundleSour
5051
return nil, err
5152
}
5253

54+
res, err := i.unpack(ctx, bundle, srcCtx, l)
55+
if err != nil {
56+
// Log any CertificateVerificationErrors, and log Docker Certificates if necessary
57+
if httputil.LogCertificateVerificationError(err, l) {
58+
httputil.LogDockerCertificates(srcCtx.DockerCertPath, l)
59+
}
60+
}
61+
return res, err
62+
}
63+
64+
func (i *ContainersImageRegistry) unpack(ctx context.Context, bundle *BundleSource, srcCtx *types.SystemContext, l logr.Logger) (*Result, error) {
5365
//////////////////////////////////////////////////////
5466
//
5567
// Resolve a canonical reference for the image.

0 commit comments

Comments
 (0)