Skip to content

Commit

Permalink
Support for minimum TLS version
Browse files Browse the repository at this point in the history
Opa server now supports min TLS version, TLS versions supported are 1.0, 1.1, 1.2, 1.3.
Since TLS 1.0 and 1.1 are deprecated, default min TLS version for opa is TLS 1.2 but
if someone wants to restrict opa to use a specific minimum TLS version, they can specify it using cmd parameter --min-tls-version

fixes open-policy-agent#3226
Signed-off-by: Amruta Kale <amruta.kale@styra.com>
  • Loading branch information
Amruta Kale authored and kale-amruta committed Jun 29, 2021
1 parent 335c10c commit b68e2f8
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 7 deletions.
4 changes: 4 additions & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type runCmdParams struct {
skipVersionCheck bool
authentication *util.EnumFlag
authorization *util.EnumFlag
minTLSVersion *util.EnumFlag
logLevel *util.EnumFlag
logFormat *util.EnumFlag
algorithm string
Expand All @@ -50,6 +51,7 @@ func newRunParams() runCmdParams {
rt: runtime.NewParams(),
authentication: util.NewEnumFlag("off", []string{"token", "tls", "off"}),
authorization: util.NewEnumFlag("off", []string{"basic", "off"}),
minTLSVersion: util.NewEnumFlag("1.2", []string{"1.0", "1.1", "1.2", "1.3"}),
logLevel: util.NewEnumFlag("info", []string{"debug", "info", "error"}),
logFormat: util.NewEnumFlag("json", []string{"text", "json", "json-pretty"}),
}
Expand Down Expand Up @@ -178,6 +180,7 @@ To skip bundle verification, use the --skip-verify flag.
runCommand.Flags().StringVarP(&cmdParams.tlsCACertFile, "tls-ca-cert-file", "", "", "set path of TLS CA cert file")
runCommand.Flags().VarP(cmdParams.authentication, "authentication", "", "set authentication scheme")
runCommand.Flags().VarP(cmdParams.authorization, "authorization", "", "set authorization scheme")
runCommand.Flags().VarP(cmdParams.minTLSVersion, "min-tls-version", "", "set minimum tls version to be used by opa server, default is 1.2")
runCommand.Flags().VarP(cmdParams.logLevel, "log-level", "l", "set log level")
runCommand.Flags().VarP(cmdParams.logFormat, "log-format", "", "set log format")
runCommand.Flags().IntVar(&cmdParams.rt.GracefulShutdownPeriod, "shutdown-grace-period", 10, "set the time (in seconds) that the server will wait to gracefully shut down")
Expand Down Expand Up @@ -235,6 +238,7 @@ func initRuntime(ctx context.Context, params runCmdParams, args []string) (*runt

params.rt.Authentication = authenticationSchemes[params.authentication.String()]
params.rt.Authorization = authorizationScheme[params.authorization.String()]
params.rt.MinTLSVersion = params.minTLSVersion.String()
params.rt.Certificate = cert
params.rt.Logging = runtime.LoggingConfig{
Level: params.logLevel.String(),
Expand Down
8 changes: 6 additions & 2 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ type Params struct {
// CertPool holds the CA certs trusted by the OPA server.
CertPool *x509.CertPool

// MinVersion contains the minimum TLS version that is acceptable.
// If zero, TLS 1.2 is currently taken as the minimum.
MinTLSVersion string

// HistoryPath is the filename to store the interactive shell user
// input history.
HistoryPath string
Expand Down Expand Up @@ -356,7 +360,6 @@ func (rt *Runtime) Serve(ctx context.Context) error {
}

defer rt.Manager.Stop(ctx)

rt.server = server.New().
WithRouter(rt.Params.Router).
WithStore(rt.Store).
Expand All @@ -372,7 +375,8 @@ func (rt *Runtime) Serve(ctx context.Context) error {
WithDecisionIDFactory(rt.decisionIDFactory).
WithDecisionLoggerWithErr(rt.decisionLogger).
WithRuntime(rt.Manager.Info).
WithMetrics(rt.metrics)
WithMetrics(rt.metrics).
WithMinTLSVersion(rt.Params.MinTLSVersion)

if rt.Params.DiagnosticAddrs != nil {
rt.server = rt.server.WithDiagnosticAddresses(*rt.Params.DiagnosticAddrs)
Expand Down
41 changes: 41 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ const (
AuthenticationTLS
)

const (

//TLSVersion10 is a constant for tls version 1.0
TLSVersion10 = "1.0"
//TLSVersion11 is a constant for tls version 1.1
TLSVersion11 = "1.1"
//TLSVersion12 is a constant for tls version 1.2
TLSVersion12 = "1.2"
//TLSVersion13 is a constant for tls version 1.3
TLSVersion13 = "1.3"
)

// AuthorizationScheme enumerates the supported authorization schemes. The authorization
// scheme determines how access to OPA is controlled.
type AuthorizationScheme int
Expand All @@ -68,6 +80,7 @@ const (
AuthorizationOff AuthorizationScheme = iota
AuthorizationBasic
)
const defaultMinTLSVersion = tls.VersionTLS12

// Set of handlers for use in the "handler" dimension of the duration metric.
const (
Expand Down Expand Up @@ -100,6 +113,7 @@ type Server struct {
authorization AuthorizationScheme
cert *tls.Certificate
certPool *x509.CertPool
minTLSVersion uint16
mtx sync.RWMutex
partials map[string]rego.PartialResult
preparedEvalQueries *cache
Expand Down Expand Up @@ -302,6 +316,11 @@ func (s *Server) WithRouter(router *mux.Router) *Server {
return s
}

func (s *Server) WithMinTLSVersion(minTLSVersion string) *Server {
s.minTLSVersion = getMinTLSVersion(minTLSVersion)
return s
}

// Listeners returns functions that listen and serve connections.
func (s *Server) Listeners() ([]Loop, error) {
loops := []Loop{}
Expand Down Expand Up @@ -463,6 +482,22 @@ func (b *baseHTTPListener) Type() httpListenerType {
return b.t
}

func getMinTLSVersion(minTLSVersion string) (minServerTlSVersion uint16) {
switch minTLSVersion {
case TLSVersion10:
minServerTlSVersion = tls.VersionTLS10
case TLSVersion11:
minServerTlSVersion = tls.VersionTLS11
case TLSVersion12:
minServerTlSVersion = tls.VersionTLS12
case TLSVersion13:
minServerTlSVersion = tls.VersionTLS13
default:
minServerTlSVersion = defaultMinTLSVersion
}
return
}

func (s *Server) getListener(addr string, h http.Handler, t httpListenerType) (Loop, httpListener, error) {
parsedURL, err := parseURL(addr, s.cert != nil)
if err != nil {
Expand Down Expand Up @@ -517,6 +552,12 @@ func (s *Server) getListenerForHTTPSServer(u *url.URL, h http.Handler, t httpLis
httpsServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
}

if s.minTLSVersion != 0 {
httpsServer.TLSConfig.MinVersion = s.minTLSVersion
} else {
httpsServer.TLSConfig.MinVersion = defaultMinTLSVersion
}

l := newHTTPListener(&httpsServer, t)

httpsLoop := func() error { return l.ListenAndServeTLS("", "") }
Expand Down
39 changes: 34 additions & 5 deletions test/e2e/tls/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,45 @@ allow {

// We need a client with proper TLS setup, otherwise the health check
// that loops to determine if the server is ready will fail.
testRuntime.Client = newClient(pool, "testdata/client-cert.pem", "testdata/client-key.pem")
testRuntime.Client = newClient(0, pool, "testdata/client-cert.pem", "testdata/client-key.pem")

os.Exit(testRuntime.RunTests(m))
}

func TestMinTLSVersion(t *testing.T) {
endpoint := testRuntime.URL()
t.Run("TLS version not suported by server", func(t *testing.T) {

c := newClient(tls.VersionTLS10, pool, "testdata/client-cert.pem", "testdata/client-key.pem")
_, err := c.Get(endpoint)
if err == nil {
t.Error("expected err - protocol version not supported, got nil")
}

})
t.Run("TLS Version supported by server", func(t *testing.T) {

c := newClient(tls.VersionTLS12, pool, "testdata/client-cert.pem", "testdata/client-key.pem")
resp, err := c.Get(endpoint)
if err != nil {
t.Fatalf("GET: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %s", resp.Status)
}
})

}

func TestAuthenticationTLS(t *testing.T) {
endpoint := testRuntime.URL() + "/v1/data/foo"

// Note: This test is redundant. When the testRuntime starts the server, it
// already queries the health endpoint using a properly authenticated, and
// authorized, http client.
t.Run("happy path", func(t *testing.T) {
c := newClient(pool, "testdata/client-cert.pem", "testdata/client-key.pem")
c := newClient(0, pool, "testdata/client-cert.pem", "testdata/client-key.pem")
resp, err := c.Get(endpoint)
if err != nil {
t.Fatalf("GET: %v", err)
Expand All @@ -101,7 +127,7 @@ func TestAuthenticationTLS(t *testing.T) {
})

t.Run("authn successful, authz failed", func(t *testing.T) {
c := newClient(pool, "testdata/client-cert-2.pem", "testdata/client-key-2.pem")
c := newClient(0, pool, "testdata/client-cert-2.pem", "testdata/client-key-2.pem")
resp, err := c.Get(endpoint)
if err != nil {
t.Fatalf("GET: %v", err)
Expand All @@ -113,15 +139,15 @@ func TestAuthenticationTLS(t *testing.T) {
})

t.Run("client trusts server, but doesn't provide client cert", func(t *testing.T) {
c := newClient(pool)
c := newClient(0, pool)
_, err := c.Get(endpoint)
if _, ok := err.(*url.Error); !ok {
t.Errorf("expected *url.Error, got %T: %v", err, err)
}
})
}

func newClient(pool *x509.CertPool, clientKeyPair ...string) *http.Client {
func newClient(maxTLSVersion uint16, pool *x509.CertPool, clientKeyPair ...string) *http.Client {
c := *http.DefaultClient
// Note: zero-values in http.Transport are bad settings -- they let the client
// leak connections -- but it's good enough for these tests. Don't instantiate
Expand All @@ -139,6 +165,9 @@ func newClient(pool *x509.CertPool, clientKeyPair ...string) *http.Client {
}
tr.TLSClientConfig.Certificates = []tls.Certificate{clientCert}
}
if maxTLSVersion != 0 {
tr.TLSClientConfig.MaxVersion = maxTLSVersion
}
c.Transport = tr
return &c
}

0 comments on commit b68e2f8

Please sign in to comment.