Skip to content

Commit ef4aa0f

Browse files
dkorunicGopher Bot
authored andcommitted
BUG/MEDIUM: storage: write ssl certificates owner-only
Certificate bundles in SSL storage include the private key but were written with mode 0644 on create and replace, readable by any local user. Write SSLType files with mode 0600. Other storage types keep 0644.
1 parent 426dc53 commit ef4aa0f

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

‎storage/ssl_mode_test.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2026 HAProxy Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
package storage
17+
18+
import (
19+
"bytes"
20+
"crypto/ecdsa"
21+
"crypto/elliptic"
22+
"crypto/rand"
23+
"crypto/x509"
24+
"crypto/x509/pkix"
25+
"encoding/pem"
26+
"io"
27+
"math/big"
28+
"os"
29+
"strings"
30+
"testing"
31+
"time"
32+
33+
"github.com/stretchr/testify/require"
34+
)
35+
36+
// Certificate bundles include the private key: owner only.
37+
func TestSSLStorage_PrivateKeyNotWorldReadable(t *testing.T) {
38+
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
39+
require.NoError(t, err)
40+
tmpl := &x509.Certificate{
41+
SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "test.local"},
42+
NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour),
43+
}
44+
der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
45+
require.NoError(t, err)
46+
kb, err := x509.MarshalECPrivateKey(key)
47+
require.NoError(t, err)
48+
var bundle bytes.Buffer
49+
require.NoError(t, pem.Encode(&bundle, &pem.Block{Type: "CERTIFICATE", Bytes: der}))
50+
require.NoError(t, pem.Encode(&bundle, &pem.Block{Type: "EC PRIVATE KEY", Bytes: kb}))
51+
52+
pemData := bundle.String()
53+
s, err := New(t.TempDir(), SSLType)
54+
require.NoError(t, err)
55+
f, _, err := s.Create("site.pem", io.NopCloser(strings.NewReader(pemData)))
56+
require.NoError(t, err)
57+
fi, err := os.Stat(f)
58+
require.NoError(t, err)
59+
require.Zero(t, fi.Mode().Perm()&0o077, "created: private key readable by group/others: %v", fi.Mode().Perm())
60+
61+
f, err = s.Replace("site.pem", pemData)
62+
require.NoError(t, err)
63+
fi, err = os.Stat(f)
64+
require.NoError(t, err)
65+
require.Zero(t, fi.Mode().Perm()&0o077, "replaced: private key readable by group/others: %v", fi.Mode().Perm())
66+
}

‎storage/storage.go‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func (s storage) Replace(name string, config string) (string, error) {
187187
case MapsType:
188188
}
189189

190-
err = renameio.WriteFile(f, []byte(config), 0o644)
190+
err = renameio.WriteFile(f, []byte(config), s.fileMode())
191191
if err != nil {
192192
return "", err
193193
}
@@ -224,7 +224,7 @@ func (s *storage) createSSL(name string, readCloser io.ReadCloser) (string, int6
224224
if err != nil {
225225
return "", -1, err
226226
}
227-
err = renameio.WriteFile(name, b, 0o644)
227+
err = renameio.WriteFile(name, b, s.fileMode())
228228
if err != nil {
229229
return "", -1, err
230230
}
@@ -358,3 +358,11 @@ func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
358358

359359
return nil, errors.New("failed to parse private key")
360360
}
361+
362+
// fileMode returns the mode of stored files: certificate bundles hold private keys.
363+
func (s *storage) fileMode() os.FileMode {
364+
if s.fileType == SSLType {
365+
return 0o600
366+
}
367+
return 0o644
368+
}

0 commit comments

Comments
 (0)