Skip to content

Commit c015edd

Browse files
authored
configure server-side TLS on grpc listener (#1252)
* configure server-side TLS on grpc listener Signed-off-by: Bob Callaway <[email protected]> * add min version as TLS 1.3 Signed-off-by: Bob Callaway <[email protected]> --------- Signed-off-by: Bob Callaway <[email protected]>
1 parent fe9bfcc commit c015edd

File tree

3 files changed

+212
-9
lines changed

3 files changed

+212
-9
lines changed

cmd/app/grpc.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package app
1717

1818
import (
1919
"context"
20+
"crypto/tls"
2021
"fmt"
2122
"net"
2223
"os"
@@ -38,6 +39,7 @@ import (
3839
"github.com/sigstore/fulcio/pkg/server"
3940
"github.com/spf13/viper"
4041
"google.golang.org/grpc"
42+
"google.golang.org/grpc/credentials"
4143
)
4244

4345
const (
@@ -65,18 +67,42 @@ func PassFulcioConfigThruContext(cfg *config.FulcioConfig) grpc.UnaryServerInter
6567
}
6668
}
6769

70+
func createGRPCCreds(certPath, keyPath string) (grpc.ServerOption, error) {
71+
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
72+
if err != nil {
73+
return nil, fmt.Errorf("loading GRPC tls certificate and key file: %w", err)
74+
}
75+
76+
return grpc.Creds(credentials.NewTLS(&tls.Config{
77+
Certificates: []tls.Certificate{cert},
78+
MinVersion: tls.VersionTLS13,
79+
})), nil
80+
}
81+
6882
func createGRPCServer(cfg *config.FulcioConfig, ctClient *ctclient.LogClient, baseca ca.CertificateAuthority, ip identity.IssuerPool) (*grpcServer, error) {
6983
logger, opts := log.SetupGRPCLogging()
7084

71-
myServer := grpc.NewServer(grpc.UnaryInterceptor(
72-
grpcmw.ChainUnaryServer(
73-
grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(panicRecoveryHandler)), // recovers from per-transaction panics elegantly, so put it first
74-
middleware.UnaryRequestID(middleware.UseXRequestIDMetadataOption(true), middleware.XRequestMetadataLimitOption(128)),
75-
grpc_zap.UnaryServerInterceptor(logger, opts...),
76-
PassFulcioConfigThruContext(cfg),
77-
grpc_prometheus.UnaryServerInterceptor,
78-
)),
79-
grpc.MaxRecvMsgSize(int(maxMsgSize)))
85+
serverOpts := []grpc.ServerOption{
86+
grpc.UnaryInterceptor(
87+
grpcmw.ChainUnaryServer(
88+
grpc_recovery.UnaryServerInterceptor(grpc_recovery.WithRecoveryHandlerContext(panicRecoveryHandler)), // recovers from per-transaction panics elegantly, so put it first
89+
middleware.UnaryRequestID(middleware.UseXRequestIDMetadataOption(true), middleware.XRequestMetadataLimitOption(128)),
90+
grpc_zap.UnaryServerInterceptor(logger, opts...),
91+
PassFulcioConfigThruContext(cfg),
92+
grpc_prometheus.UnaryServerInterceptor,
93+
)),
94+
grpc.MaxRecvMsgSize(int(maxMsgSize)),
95+
}
96+
97+
if viper.IsSet("grpc-tls-certificate") && viper.IsSet("grpc-tls-key") {
98+
creds, err := createGRPCCreds(viper.GetString("grpc-tls-certificate"), viper.GetString("grpc-tls-key"))
99+
if err != nil {
100+
return nil, err
101+
}
102+
serverOpts = append(serverOpts, creds)
103+
}
104+
105+
myServer := grpc.NewServer(serverOpts...)
80106

81107
grpcCAServer := server.NewGRPCCAServer(ctClient, baseca, ip)
82108
// Register your gRPC service implementations.

cmd/app/grpc_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright 2023 The Sigstore Authors.
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 app
17+
18+
import (
19+
"os"
20+
"path/filepath"
21+
"testing"
22+
)
23+
24+
const keyPEM = `-----BEGIN PRIVATE KEY-----
25+
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
74+
qYtj1Qkj5x+/YNNEPPyaPZ54nh3F
75+
-----END PRIVATE KEY-----`
76+
77+
const certPEM = `-----BEGIN CERTIFICATE-----
78+
MIIFqzCCA5OgAwIBAgIUTmTZD9FuR4UNUNVOkB2IMeWqv1UwDQYJKoZIhvcNAQEL
79+
BQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMREwDwYDVQQHDAhLaXJrbGFu
80+
ZDERMA8GA1UECgwIU2lnc3RvcmUxDzANBgNVBAsMBkZ1bGNpbzESMBAGA1UEAwwJ
81+
bG9jYWxob3N0MB4XDTIzMDYyODAwMTQzMloXDTMzMDYyNTAwMTQzMlowZTELMAkG
82+
A1UEBhMCVVMxCzAJBgNVBAgMAldBMREwDwYDVQQHDAhLaXJrbGFuZDERMA8GA1UE
83+
CgwIU2lnc3RvcmUxDzANBgNVBAsMBkZ1bGNpbzESMBAGA1UEAwwJbG9jYWxob3N0
84+
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvc3MNQYRO5ytG+8FsPn5
85+
0Z8koUG4sYPf6ZLTMP78+sRYfz2ggZaP46Hl3f571AB8nXSBRbIc9byDgOVpOs9t
86+
zRnYA6tyv9cgtOCtgFfuctHFdeWpJXQr4wWhB0oUspmu66cmFKYfdwrfnrjvRkZ2
87+
+33fGK0hC6EXtNIX7sg+Y98jT1iW0AIiBZxMBf8p5d5fTEfodVN/NZ0FN58/TOPO
88+
jmkStqta+fZrPL02TVZ/IdDx7RSWVqD+KcqOJdSneuCt5qniQgcLwBMIk5ymyMLP
89+
+yKl1GQXHfagxh3e84HlE93XgVCThk9XK8HSQerUmH0oklIx2PgcSS0FPjsz49Us
90+
Kd7QweZAzcaaaoapK0QkXRvUZLSwPgOjriJtd6Pi5S7xN91DnGmyDHA7BGgCtew7
91+
1BUgW2AzWXJq3EX0kHjMaEgHCL59SUw/pOlMiNXMC6hnUSH5lnY2isNs9+DUU+Xa
92+
/Z9ME+B0SiRCaRGq7ZUdXiHuaN+DiRj3hX1VO96wVjvZAh0JklI6pVB7cz6HvOwx
93+
iAtSiXxqIQZkyac3lP939tAFzLVvpqSqaHzUF8bqBSkWxy8iZVW9EJiIb8wAVE8R
94+
Sl4WssnUrneMfXjxsyQ271H6DIDLWP4BHtorqcN0vGnOE37N6DjrOJaaTyaThn2q
95+
Kjmt6ghqvTY0CRVpyQz3szUCAwEAAaNTMFEwHQYDVR0OBBYEFGgoph9DIwXUHUT0
96+
8y7CtcviGmPhMB8GA1UdIwQYMBaAFGgoph9DIwXUHUT08y7CtcviGmPhMA8GA1Ud
97+
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAIzLEKyA1KAEgHmH4/gipKct
98+
qMYfmTPTgKm3s5sEQlFZVQS9BzjgAnHq943JDn/GqPOipAZUw3iE3sueZSVypwEi
99+
zSjbJTPJPZn4Z/RcTK8ovtjVJDUUPxAe9ScpA8YShcwcITxiXt+CzLm4lSs67kDP
100+
BqOSfrjnCSazjtFJnTEpysynTe9B8hD9ODWm4k+/hh+PGB3fqi7ZJm0+9fMr86lR
101+
QW6ZMvwUXfWUVCfalOINk17Z42hXk4+jhj8mjnMtflt9o4xPdSl3qkVwoUFRCwfy
102+
RMYL+gaKONClOogxa/sMgqycENH31DJX+lPwXUFQ+bH2SKnz3+Dgmds5O8EgheUL
103+
BDQ1Tbz7d4MTAgo/ZefZE94ZBfcz6KNwgTcxlLfl3mBc5bxhri8Y1aR8tlhOV5JS
104+
hRa/vAPlpdglBFTO0wThjJEy0xlGlUQgQ1Y1HrqATOO6ACxkbX7DEouezRe0q6e5
105+
VOUjno7qtSq6Sgj5ufWaI3qyZhJtCa0db1p6xnjIzwCblhrFKtJObqQI1Rr02Wz+
106+
H165IZ5Lwe4VyrYGTPJTzK8f0NmwVGcgB0llNi8jdmKuLc/MWuBkKdW0FtUksfzv
107+
tSLmTWsb+j/Oxljalf+rAlItYk297HN0xMvlkHkB80O5Un6OMCHAjJmfOVZal2Y5
108+
o4ZDR+PzKEbU8eUQbooS
109+
-----END CERTIFICATE-----`
110+
111+
func TestCreateGRPCCreds(t *testing.T) {
112+
dir := t.TempDir()
113+
114+
// not PKI material
115+
bad := []byte("not PKI material")
116+
badPath := filepath.Join(dir, "bad.pem")
117+
os.WriteFile(badPath, bad, 0644)
118+
119+
// priv key
120+
keyPath := filepath.Join(dir, "key.pem")
121+
os.WriteFile(keyPath, []byte(keyPEM), 0644)
122+
123+
// cert
124+
certPath := filepath.Join(dir, "cert.pem")
125+
os.WriteFile(certPath, []byte(certPEM), 0644)
126+
127+
type testCase struct {
128+
desc string
129+
keyPath string
130+
certPath string
131+
success bool
132+
}
133+
134+
testCases := []testCase{
135+
{
136+
desc: "invalid path",
137+
keyPath: filepath.Join(dir, "not_here"),
138+
certPath: badPath,
139+
success: false,
140+
},
141+
{
142+
desc: "invalid key and cert",
143+
keyPath: badPath,
144+
certPath: badPath,
145+
success: false,
146+
},
147+
{
148+
desc: "invalid key, valid cert",
149+
keyPath: badPath,
150+
certPath: certPath,
151+
success: false,
152+
},
153+
{
154+
desc: "valid key, invalid cert",
155+
keyPath: keyPath,
156+
certPath: badPath,
157+
success: false,
158+
},
159+
{
160+
desc: "valid key, valid cert",
161+
keyPath: keyPath,
162+
certPath: certPath,
163+
success: true,
164+
},
165+
}
166+
167+
for _, tc := range testCases {
168+
t.Run(tc.desc, func(t *testing.T) {
169+
_, err := createGRPCCreds(tc.certPath, tc.keyPath)
170+
if tc.success != (err == nil) {
171+
t.Errorf("unexpected result: %v", err)
172+
}
173+
})
174+
}
175+
}

cmd/app/serve.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ func newServeCmd() *cobra.Command {
100100
cmd.Flags().String("grpc-port", "8081", "The port on which to serve requests for GRPC")
101101
cmd.Flags().String("metrics-port", "2112", "The port on which to serve prometheus metrics endpoint")
102102
cmd.Flags().Duration("read-header-timeout", 10*time.Second, "The time allowed to read the headers of the requests in seconds")
103+
cmd.Flags().String("grpc-tls-certificate", "", "the certificate file to use for secure connections - only applies to grpc-port")
104+
cmd.Flags().String("grpc-tls-key", "", "the private key file to use for secure connections (without passphrase) - only applies to grpc-port")
103105

104106
// convert "http-host" flag to "host" and "http-port" flag to be "port"
105107
cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {

0 commit comments

Comments
 (0)