From cd9077c1c64149193844306e2af3c36aea43535e Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Tue, 14 Nov 2023 11:49:21 -0500 Subject: [PATCH] cliccl: Add debug enterprise-check-fips command This command reports on the status of certain prerequisites for our fips-ready builds. Updates #114344 Release note (cli change): New command `cockroach debug enterprise-check-fips` diagnoses errors in FIPS deployments --- .github/CODEOWNERS | 1 + pkg/BUILD.bazel | 1 + pkg/ccl/cliccl/BUILD.bazel | 2 + pkg/ccl/cliccl/debug.go | 56 +++++++++++++++ pkg/ccl/securityccl/fipsccl/BUILD.bazel | 17 +++++ pkg/ccl/securityccl/fipsccl/build_boring.go | 72 +++++++++++++++++++ pkg/ccl/securityccl/fipsccl/build_noboring.go | 29 ++++++++ pkg/ccl/securityccl/fipsccl/fips_linux.go | 34 +++++++++ pkg/ccl/securityccl/fipsccl/fips_nolinux.go | 17 +++++ 9 files changed, 229 insertions(+) create mode 100644 pkg/ccl/securityccl/fipsccl/BUILD.bazel create mode 100644 pkg/ccl/securityccl/fipsccl/build_boring.go create mode 100644 pkg/ccl/securityccl/fipsccl/build_noboring.go create mode 100644 pkg/ccl/securityccl/fipsccl/fips_linux.go create mode 100644 pkg/ccl/securityccl/fipsccl/fips_nolinux.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 770d30612550..547ecae7582a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -530,6 +530,7 @@ /pkg/scheduledjobs/ @cockroachdb/jobs-prs @cockroachdb/disaster-recovery /pkg/security/ @cockroachdb/prodsec @cockroachdb/server-prs /pkg/security/clientsecopts/ @cockroachdb/sql-foundations @cockroachdb/prodsec +/pkg/ccl/securityccl/ @cockroachdb/prodsec #!/pkg/settings/ @cockroachdb/unowned /pkg/spanconfig/ @cockroachdb/kv-prs @cockroachdb/sql-foundations /pkg/spanconfig/spanconfigbounds/ @cockroachdb/sql-foundations diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index a2dee9c8195f..6b564794c7db 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -894,6 +894,7 @@ GO_TARGETS = [ "//pkg/ccl/pgcryptoccl:pgcryptoccl_test", "//pkg/ccl/schemachangerccl:schemachangerccl", "//pkg/ccl/schemachangerccl:schemachangerccl_test", + "//pkg/ccl/securityccl/fipsccl:fipsccl", "//pkg/ccl/serverccl/adminccl:adminccl_test", "//pkg/ccl/serverccl/diagnosticsccl:diagnosticsccl_test", "//pkg/ccl/serverccl/statusccl:statusccl_test", diff --git a/pkg/ccl/cliccl/BUILD.bazel b/pkg/ccl/cliccl/BUILD.bazel index f4a428830232..ec1d26b52e0f 100644 --- a/pkg/ccl/cliccl/BUILD.bazel +++ b/pkg/ccl/cliccl/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "//pkg/base", "//pkg/ccl/baseccl", "//pkg/ccl/cliccl/cliflagsccl", + "//pkg/ccl/securityccl/fipsccl", "//pkg/ccl/sqlproxyccl", "//pkg/ccl/sqlproxyccl/tenantdirsvr", "//pkg/ccl/storageccl/engineccl/enginepbccl", @@ -41,6 +42,7 @@ go_library( "@com_github_cockroachdb_errors//oserror", "@com_github_cockroachdb_pebble//vfs", "@com_github_cockroachdb_redact//:redact", + "@com_github_olekukonko_tablewriter//:tablewriter", "@com_github_spf13_cobra//:cobra", ], ) diff --git a/pkg/ccl/cliccl/debug.go b/pkg/ccl/cliccl/debug.go index 3fa0464cef8e..22be71676a43 100644 --- a/pkg/ccl/cliccl/debug.go +++ b/pkg/ccl/cliccl/debug.go @@ -14,12 +14,14 @@ import ( "fmt" "os" "path/filepath" + "runtime" "sort" "time" "github.com/cockroachdb/cockroach/pkg/base" "github.com/cockroachdb/cockroach/pkg/ccl/baseccl" "github.com/cockroachdb/cockroach/pkg/ccl/cliccl/cliflagsccl" + "github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl" "github.com/cockroachdb/cockroach/pkg/ccl/storageccl/engineccl/enginepbccl" "github.com/cockroachdb/cockroach/pkg/cli" "github.com/cockroachdb/cockroach/pkg/cli/clierrorplus" @@ -31,6 +33,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/errors" "github.com/cockroachdb/errors/oserror" + "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" ) @@ -102,12 +105,24 @@ with their env type and encryption settings (if applicable). RunE: clierrorplus.MaybeDecorateError(runList), } + checkFipsCmd := &cobra.Command{ + Use: "enterprise-check-fips", + Short: "print diagnostics for FIPS-ready configuration", + Long: ` +Performs various tests of this binary's ability to operate in FIPS-ready +mode in the current environment. +`, + + RunE: clierrorplus.MaybeDecorateError(runCheckFips), + } + // Add commands to the root debug command. // We can't add them to the lists of commands (eg: DebugCmdsForPebble) as cli init() is called before us. cli.DebugCmd.AddCommand(encryptionStatusCmd) cli.DebugCmd.AddCommand(encryptionActiveKeyCmd) cli.DebugCmd.AddCommand(encryptionDecryptCmd) cli.DebugCmd.AddCommand(encryptionRegistryList) + cli.DebugCmd.AddCommand(checkFipsCmd) // Add the encryption flag to commands that need it. // For the encryption-status command. @@ -376,3 +391,44 @@ func getActiveEncryptionkey(dir string) (string, string, error) { return setting.EncryptionType.String(), setting.KeyId, nil } + +func runCheckFips(cmd *cobra.Command, args []string) error { + if runtime.GOOS != "linux" { + return errors.New("FIPS-ready mode is only supported on linux") + } + // Our FIPS-ready deployments have three major requirements: + // 1. This binary is built with the golang-fips toolchain and running on linux + // 2. FIPS mode is enabled in the kernel. + // 3. We can dynamically load the OpenSSL library (which must be the same major version that was present at + // build time). Verifying that the OpenSSL library is FIPS-compliant is outside the scope of this command. + table := tablewriter.NewWriter(os.Stdout) + table.SetBorder(false) + table.SetAlignment(tablewriter.ALIGN_LEFT) + emit := func(label string, status bool, detail string) { + statusSymbol := "❌" + if status { + statusSymbol = "✅" + } + table.Append([]string{label, statusSymbol, detail}) + } + + emit("FIPS-ready build", fipsccl.IsCompileTimeFIPSReady(), "") + buildOpenSSLVersion, soname, err := fipsccl.BuildOpenSSLVersion() + if err == nil { + table.Append([]string{"Build-time OpenSSL Version", "", buildOpenSSLVersion}) + table.Append([]string{"OpenSSL library filename", "", soname}) + } + + isKernelEnabled, err := fipsccl.IsKernelEnabled() + detail := "" + if err != nil { + detail = err.Error() + } + emit("Kernel FIPS mode enabled", isKernelEnabled, detail) + + emit("OpenSSL loaded", fipsccl.IsOpenSSLLoaded(), "") + emit("FIPS ready", fipsccl.IsFIPSReady(), "") + + table.Render() + return nil +} diff --git a/pkg/ccl/securityccl/fipsccl/BUILD.bazel b/pkg/ccl/securityccl/fipsccl/BUILD.bazel new file mode 100644 index 000000000000..ed17d4a9ccec --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/BUILD.bazel @@ -0,0 +1,17 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "fipsccl", + srcs = [ + "build_boring.go", # keep + "build_noboring.go", + "fips_linux.go", + "fips_nolinux.go", + ], + cgo = True, + importpath = "github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl", + visibility = ["//visibility:public"], + deps = [ + "@com_github_cockroachdb_errors//:errors", + ], +) diff --git a/pkg/ccl/securityccl/fipsccl/build_boring.go b/pkg/ccl/securityccl/fipsccl/build_boring.go new file mode 100644 index 000000000000..011928745400 --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/build_boring.go @@ -0,0 +1,72 @@ +// Copyright 2023 The Cockroach Authors. +// +// Licensed as a CockroachDB Enterprise file under the Cockroach Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt +// +//go:build boringcrypto + +package fipsccl + +/* +#include + +static unsigned long _fipsccl_openssl_version_number() { + return OPENSSL_VERSION_NUMBER; +} +*/ +import "C" + +import ( + "crypto/boring" + "fmt" +) + +// IsCompileTimeFIPSReady returns true if this binary was built with correct +// toolchain and options, which is a prerequisite for FIPS-ready mode. +// Note that we only support the golang-fips toolchain even though the +// build tag we test for is "boringcrypto". The two are not actually +// compatible because crypto/boring.Enabled is a bool in one and a function +// in the other. +func IsCompileTimeFIPSReady() bool { + return true +} + +// IsOpenSSLLoaded returns true if the OpenSSL library has been found and +// loaded. +func IsOpenSSLLoaded() bool { + return boring.Enabled() +} + +// IsFIPSReady returns true if all of our FIPS readiness checks succeed. +func IsFIPSReady() bool { + // The golang-fips toolchain only attempts to load OpenSSL if the kernel + // fips mode is enabled. Therefore we only need this single check for our + // overall fips-readiness status. We could redundantly call IsBoringBuild + // and IsKernelEnabled, but doing so would risk some divergence between our + // implementation and the toolchain itself so it's better at this time to + // use the single check. + return IsOpenSSLLoaded() +} + +// BuildOpenSSLVersion returns the version number of OpenSSL that was used at +// build time. The first return value is the hex value of the +// OPENSSL_VERSION_NUMBER constant (for example, 10100000 for OpenSSL 1.1 and +// 30000000 for OpenSSL 3.0), and the second is the versioned name of the +// libcrypto.so file. +func BuildOpenSSLVersion() (string, string, error) { + buildVersion := uint64(C._fipsccl_openssl_version_number()) + var soname string + // Reference: + // https://github.com/golang-fips/go/blob/7f64529ab80e5d394bb2496e982d6f6e11023902/patches/001-initial-openssl-for-fips.patch#L3476-L3482 + if buildVersion < 0x10100000 { + soname = "libcrypto.so.10" + } else if buildVersion < 0x30000000 { + soname = "libcrypto.so.1.1" + } else { + soname = "libcrypto.so.3" + } + return fmt.Sprintf("%x", buildVersion), soname, nil +} diff --git a/pkg/ccl/securityccl/fipsccl/build_noboring.go b/pkg/ccl/securityccl/fipsccl/build_noboring.go new file mode 100644 index 000000000000..e80044530ff2 --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/build_noboring.go @@ -0,0 +1,29 @@ +// Copyright 2023 The Cockroach Authors. +// +// Licensed as a CockroachDB Enterprise file under the Cockroach Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt +// +//go:build !boringcrypto + +package fipsccl + +import "github.com/cockroachdb/errors" + +func IsCompileTimeFIPSReady() bool { + return false +} + +func IsOpenSSLLoaded() bool { + return false +} + +func IsFIPSReady() bool { + return false +} + +func BuildOpenSSLVersion() (string, string, error) { + return "", "", errors.New("openssl support not present") +} diff --git a/pkg/ccl/securityccl/fipsccl/fips_linux.go b/pkg/ccl/securityccl/fipsccl/fips_linux.go new file mode 100644 index 000000000000..6d841573bcbd --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/fips_linux.go @@ -0,0 +1,34 @@ +// Copyright 2023 The Cockroach Authors. +// +// Licensed as a CockroachDB Enterprise file under the Cockroach Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt + +package fipsccl + +import ( + "fmt" + "os" + + "github.com/cockroachdb/errors" +) + +const fipsSysctlFilename = "/proc/sys/crypto/fips_enabled" + +// IsKernelEnabled returns true if FIPS mode is enabled in the kernel +// (by reading the crypto.fips_enabled sysctl). +func IsKernelEnabled() (bool, error) { + data, err := os.ReadFile(fipsSysctlFilename) + if err != nil { + return false, err + } + if len(data) == 0 { + return false, errors.New("sysctl file empty") + } + if data[0] == '1' { + return true, nil + } + return false, fmt.Errorf("sysctl value: %q", data) +} diff --git a/pkg/ccl/securityccl/fipsccl/fips_nolinux.go b/pkg/ccl/securityccl/fipsccl/fips_nolinux.go new file mode 100644 index 000000000000..247548b03b25 --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/fips_nolinux.go @@ -0,0 +1,17 @@ +// Copyright 2023 The Cockroach Authors. +// +// Licensed as a CockroachDB Enterprise file under the Cockroach Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt +// +//go:build !linux + +package fipsccl + +import "github.com/cockroachdb/errors" + +func IsKernelEnabled() (bool, error) { + return false, errors.New("only supported on linux") +}