Skip to content

Commit

Permalink
Workaround system cert reloading after import (bsc#1195220)
Browse files Browse the repository at this point in the history
This is a workaround for Golang's missing feature of reloading system
certs.
The built in system certs pool is initialized only once on startup and
there is no option for reloading it when new certs are installed.
The problem is known upstream and tracked at
golang/go#41888.
The cert_pool.go is almost 1:1 copy of original library code but exposes
needed functionality. It can be removed when upstream fixes the problem.
  • Loading branch information
skazi0 committed Feb 4, 2022
1 parent f7f2d82 commit 87b7f53
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 2 deletions.
131 changes: 131 additions & 0 deletions internal/connect/cert_pool.go
@@ -0,0 +1,131 @@
// TODO: remove when https://github.com/golang/go/issues/41888 is fixed
// code copied from standard library crypto/x509/root.go to enable system certs reloading

package connect

import (
"crypto/x509"
"io/fs"
"os"
"path/filepath"
"strings"
)

func systemRootsPool() *x509.CertPool {
systemRoots, systemRootsErr := _loadSystemRoots()
if systemRootsErr != nil {
return nil
}
return systemRoots
}

const (
// certFileEnv is the environment variable which identifies where to locate
// the SSL certificate file. If set this overrides the system default.
_certFileEnv = "SSL_CERT_FILE"

// certDirEnv is the environment variable which identifies which directory
// to check for SSL certificate files. If set this overrides the system default.
// It is a colon separated list of directories.
// See https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html.
_certDirEnv = "SSL_CERT_DIR"
)

// Possible certificate files; stop after finding one.
var _certFiles = []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
"/etc/ssl/cert.pem", // Alpine Linux
}

// Possible directories with certificate files; stop after successfully
// reading at least one file from a directory.
var _certDirectories = []string{
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
"/etc/pki/tls/certs", // Fedora/RHEL
"/system/etc/security/cacerts", // Android
}

func _loadSystemRoots() (*x509.CertPool, error) {
roots := x509.NewCertPool()
rootsLen := 0

files := _certFiles
if f := os.Getenv(_certFileEnv); f != "" {
files = []string{f}
}

var firstErr error
for _, file := range files {
data, err := os.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
rootsLen++
break
}
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
}
}

dirs := _certDirectories
if d := os.Getenv(_certDirEnv); d != "" {
// OpenSSL and BoringSSL both use ":" as the SSL_CERT_DIR separator.
// See:
// * https://golang.org/issue/35325
// * https://www.openssl.org/docs/man1.0.2/man1/c_rehash.html
dirs = strings.Split(d, ":")
}

for _, directory := range dirs {
fis, err := _readUniqueDirectoryEntries(directory)
if err != nil {
if firstErr == nil && !os.IsNotExist(err) {
firstErr = err
}
continue
}
for _, fi := range fis {
data, err := os.ReadFile(directory + "/" + fi.Name())
if err == nil {
roots.AppendCertsFromPEM(data)
rootsLen++
}
}
}

if rootsLen > 0 || firstErr == nil {
return roots, nil
}

return nil, firstErr
}

// readUniqueDirectoryEntries is like os.ReadDir but omits
// symlinks that point within the directory.
func _readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
uniq := files[:0]
for _, f := range files {
if !_isSameDirSymlink(f, dir) {
uniq = append(uniq, f)
}
}
return uniq, nil
}

// isSameDirSymlink reports whether fi in dir is a symlink with a
// target not containing a slash.
func _isSameDirSymlink(f fs.DirEntry, dir string) bool {
if f.Type()&fs.ModeSymlink == 0 {
return false
}
target, err := os.Readlink(filepath.Join(dir, f.Name()))
return err == nil && !strings.Contains(target, "/")
}
9 changes: 8 additions & 1 deletion internal/connect/connection.go
Expand Up @@ -89,7 +89,7 @@ func setupHTTPClient() {
if httpclient == nil {
// use defaults from DefaultTransport
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: CFG.Insecure}
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: CFG.Insecure, RootCAs: systemRootsPool()}
tr.Proxy = proxyWithAuth
httpclient = &http.Client{Transport: tr, Timeout: 60 * time.Second}
}
Expand Down Expand Up @@ -147,3 +147,10 @@ func callHTTP(verb, path string, body []byte, query map[string]string, auth auth
}
return resBody, nil
}

func reloadCertPool() error {
// TODO: update when https://github.com/golang/go/issues/41888 is fixed
httpclient = nil
setupHTTPClient()
return nil
}
6 changes: 5 additions & 1 deletion internal/connect/system.go
Expand Up @@ -118,5 +118,9 @@ func Cleanup() error {
func UpdateCertificates() error {
cmd := []string{"/usr/sbin/update-ca-certificates"}
_, err := execute(cmd, []int{0})
return err
if err != nil {
return err
}
// reload CA certs in Go
return reloadCertPool()
}

0 comments on commit 87b7f53

Please sign in to comment.