Skip to content

Commit 4f54d76

Browse files
authored
Merge pull request #4138 from apostasie/tigron-2025-04-x509-helper
[Tigron]: `x509` helper
2 parents 8790fae + 15bb28e commit 4f54d76

File tree

7 files changed

+205
-184
lines changed

7 files changed

+205
-184
lines changed

Diff for: mod/tigron/utils/testca/ca.go

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package testca provides helpers to create a self-signed CA certificate, and the ability to generate
18+
// signed certificates from it.
19+
// PLEASE NOTE THIS IS NOT A PRODUCTION SAFE NOR VERIFIED WAY TO MANAGE CERTIFICATES FOR SERVERS.
20+
package testca
21+
22+
import (
23+
"crypto/rand"
24+
"crypto/rsa"
25+
"crypto/x509"
26+
"crypto/x509/pkix"
27+
"encoding/pem"
28+
"io"
29+
"math/big"
30+
"net"
31+
"time"
32+
33+
"github.com/containerd/nerdctl/mod/tigron/internal/assertive"
34+
"github.com/containerd/nerdctl/mod/tigron/test"
35+
"github.com/containerd/nerdctl/mod/tigron/tig"
36+
)
37+
38+
const (
39+
keyLength = 4096
40+
caRoot = "ca"
41+
certsRoot = "certs"
42+
organization = "tigron volatile testing organization"
43+
lifetime = 24 * time.Hour
44+
serialSize = 60
45+
)
46+
47+
// NewX509 creates a new, self-signed, signing certificate under data.Temp()/ca
48+
// From that Cert as a CA, you can then generate signed certificates.
49+
// Note that the common name of the cert will be set to the test name.
50+
func NewX509(data test.Data, helpers test.Helpers) *Cert {
51+
template := &x509.Certificate{
52+
Subject: pkix.Name{
53+
Organization: []string{organization},
54+
CommonName: helpers.T().Name(),
55+
},
56+
NotBefore: time.Now(),
57+
NotAfter: time.Now().Add(lifetime),
58+
IsCA: true,
59+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
60+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
61+
BasicConstraintsValid: true,
62+
}
63+
64+
return (&Cert{}).GenerateCustomX509(data, helpers, caRoot, template)
65+
}
66+
67+
// Cert allows the consumer to retrieve the cert and key path, to be used by other processes, like servers for example.
68+
type Cert struct {
69+
KeyPath string
70+
CertPath string
71+
key *rsa.PrivateKey
72+
cert *x509.Certificate
73+
}
74+
75+
// GenerateServerX509 produces a certificate usable by a server.
76+
// additional can be used to provide additional ips to be added to the certificate.
77+
func (ca *Cert) GenerateServerX509(data test.Data, helpers test.Helpers, host string, additional ...string) *Cert {
78+
template := &x509.Certificate{
79+
Subject: pkix.Name{
80+
Organization: []string{organization},
81+
CommonName: host,
82+
},
83+
NotBefore: time.Now(),
84+
NotAfter: time.Now().Add(lifetime),
85+
KeyUsage: x509.KeyUsageCRLSign,
86+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
87+
DNSNames: additional,
88+
}
89+
90+
additional = append([]string{host}, additional...)
91+
for _, h := range additional {
92+
if ip := net.ParseIP(h); ip != nil {
93+
template.IPAddresses = append(template.IPAddresses, ip)
94+
}
95+
}
96+
97+
return ca.GenerateCustomX509(data, helpers, certsRoot, template)
98+
}
99+
100+
// GenerateCustomX509 signs a random x509 certificate template.
101+
// Note that if SerialNumber is specified, it must be safe to use on the filesystem as this will be used in the name
102+
// of the certificate file.
103+
func (ca *Cert) GenerateCustomX509(
104+
data test.Data,
105+
helpers test.Helpers,
106+
underDirectory string,
107+
template *x509.Certificate,
108+
) *Cert {
109+
silentT := assertive.WithSilentSuccess(helpers.T())
110+
key, certPath, keyPath := createCert(silentT, data, underDirectory, template, ca.cert, ca.key)
111+
112+
return &Cert{
113+
CertPath: certPath,
114+
KeyPath: keyPath,
115+
key: key,
116+
cert: template,
117+
}
118+
}
119+
120+
func createCert(
121+
testing tig.T,
122+
data test.Data,
123+
dir string,
124+
template, caCert *x509.Certificate,
125+
caKey *rsa.PrivateKey,
126+
) (key *rsa.PrivateKey, certPath, keyPath string) {
127+
if caCert == nil {
128+
caCert = template
129+
}
130+
131+
if caKey == nil {
132+
caKey = key
133+
}
134+
135+
key, err := rsa.GenerateKey(rand.Reader, keyLength)
136+
assertive.ErrorIsNil(testing, err, "key generation should succeed")
137+
138+
signedCert, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
139+
assertive.ErrorIsNil(testing, err, "certificate creation should succeed")
140+
141+
serial := template.SerialNumber
142+
if serial == nil {
143+
serial = serialNumber()
144+
}
145+
146+
data.Temp().Dir(dir)
147+
certPath = data.Temp().Path(dir, serial.String()+".cert")
148+
keyPath = data.Temp().Path(dir, serial.String()+".key")
149+
150+
data.Temp().SaveToWriter(func(writer io.Writer) error {
151+
return pem.Encode(writer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
152+
}, keyPath)
153+
154+
data.Temp().SaveToWriter(func(writer io.Writer) error {
155+
return pem.Encode(writer, &pem.Block{Type: "CERTIFICATE", Bytes: signedCert})
156+
}, keyPath)
157+
158+
return key, certPath, keyPath
159+
}
160+
161+
func serialNumber() *big.Int {
162+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), serialSize)
163+
164+
serial, err := rand.Int(rand.Reader, serialNumberLimit)
165+
if err != nil {
166+
panic(err)
167+
}
168+
169+
return serial
170+
}

Diff for: pkg/containerdutil/image_store.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package containerdutil
18+
19+
type ImageStore struct {
20+
}

Diff for: pkg/testutil/nerdtest/ca/ca.go

-161
This file was deleted.

Diff for: pkg/testutil/nerdtest/registry/cesanta.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import (
3131

3232
"github.com/containerd/nerdctl/mod/tigron/expect"
3333
"github.com/containerd/nerdctl/mod/tigron/test"
34+
"github.com/containerd/nerdctl/mod/tigron/utils/testca"
3435

3536
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
36-
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/ca"
3737
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/platform"
3838
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
3939
"github.com/containerd/nerdctl/v2/pkg/testutil/portlock"
@@ -119,7 +119,7 @@ func ensureContainerStarted(helpers test.Helpers, con string) {
119119
}
120120
}
121121

122-
func NewCesantaAuthServer(data test.Data, helpers test.Helpers, ca *ca.CA, port int, user, pass string, tls bool) *TokenAuthServer {
122+
func NewCesantaAuthServer(data test.Data, helpers test.Helpers, ca *testca.Cert, port int, user, pass string, tls bool) *TokenAuthServer {
123123
// listen on 0.0.0.0 to enable 127.0.0.1
124124
listenIP := net.ParseIP("0.0.0.0")
125125
hostIP, err := nettestutil.NonLoopbackIPv4()
@@ -165,7 +165,7 @@ func NewCesantaAuthServer(data test.Data, helpers test.Helpers, ca *ca.CA, port
165165
err = cc.Save(configFileName)
166166
assert.NilError(helpers.T(), err, fmt.Errorf("failed writing configuration: %w", err))
167167

168-
cert := ca.NewCert(hostIP.String())
168+
cert := ca.GenerateServerX509(data, helpers, hostIP.String())
169169
// FIXME: this will fail in many circumstances. Review strategy on how to acquire a free port.
170170
// We probably have better code for that already somewhere.
171171
port, err = portlock.Acquire(port)
@@ -177,13 +177,9 @@ func NewCesantaAuthServer(data test.Data, helpers test.Helpers, ca *ca.CA, port
177177
cleanup := func(data test.Data, helpers test.Helpers) {
178178
helpers.Ensure("rm", "-f", containerName)
179179
errPortRelease := portlock.Release(port)
180-
errCertClose := cert.Close()
181180
if errPortRelease != nil {
182181
helpers.T().Error(errPortRelease.Error())
183182
}
184-
if errCertClose != nil {
185-
helpers.T().Error(errCertClose.Error())
186-
}
187183
}
188184

189185
setup := func(data test.Data, helpers test.Helpers) {

0 commit comments

Comments
 (0)