diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dfbf1e0f5bef..3f1974b07cbc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -516,6 +516,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/WORKSPACE b/WORKSPACE index 2f31c55ae56e..54458d46299e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -620,4 +620,14 @@ go_download_sdk( }, urls = ["https://storage.googleapis.com/public-bazel-artifacts/go/20231206-175156/{}"], version = "1.21.5fips", + # In the golang-fips toolchain, FIPS-ready crypto packages are used by default, regardless of build tags. + # The boringcrypto experiment does almost nothing in this toolchain, but it does enable the use of the + # crypto/boring.Enabled() method which is the only application-visible way to inspect whether FIPS mode + # is working correctly. + # + # The golang-fips toolchain also supports an experiment `strictfipsruntime` which causes a panic at startup + # if the kernel is in FIPS mode but OpenSSL cannot be loaded. We do not currently use this experiment + # because A) we also want to detect the case when the kernel is not in FIPS mode and B) we want to be + # able to provide additional diagnostic information such as the expected version of OpenSSL. + experiments = ["boringcrypto"], ) diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index 29ab73d28360..f9a61ca6139e 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -889,6 +889,7 @@ GO_TARGETS = [ "//pkg/ccl/plpgsqlccl:plpgsqlccl", "//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/BUILD.bazel b/pkg/ccl/BUILD.bazel index c3588e9a3227..494a6fd5eef6 100644 --- a/pkg/ccl/BUILD.bazel +++ b/pkg/ccl/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "//pkg/ccl/partitionccl", "//pkg/ccl/pgcryptoccl", "//pkg/ccl/plpgsqlccl", + "//pkg/ccl/securityccl/fipsccl", "//pkg/ccl/storageccl", "//pkg/ccl/storageccl/engineccl", "//pkg/ccl/streamingccl/streamingest", diff --git a/pkg/ccl/ccl_init.go b/pkg/ccl/ccl_init.go index 0a35c16ba47e..fc315a0a087f 100644 --- a/pkg/ccl/ccl_init.go +++ b/pkg/ccl/ccl_init.go @@ -28,6 +28,7 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/pgcryptoccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/plpgsqlccl" + _ "github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/storageccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/storageccl/engineccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/streamingccl/streamingest" diff --git a/pkg/ccl/cliccl/BUILD.bazel b/pkg/ccl/cliccl/BUILD.bazel index 8aae6a3802fb..9b66fdd30fee 100644 --- a/pkg/ccl/cliccl/BUILD.bazel +++ b/pkg/ccl/cliccl/BUILD.bazel @@ -20,16 +20,19 @@ 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", "//pkg/ccl/utilccl", "//pkg/ccl/workloadccl/cliccl", "//pkg/cli", + "//pkg/cli/clierror", "//pkg/cli/clierrorplus", "//pkg/cli/cliflagcfg", "//pkg/cli/cliflags", "//pkg/cli/democluster", + "//pkg/cli/exit", "//pkg/storage", "//pkg/storage/enginepb", "//pkg/util/log", @@ -41,7 +44,9 @@ 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", + "@com_github_spf13_pflag//:pflag", ], ) 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/cliccl/flags.go b/pkg/ccl/cliccl/flags.go index 58bd3e8d99ba..0c19bd81e0b5 100644 --- a/pkg/ccl/cliccl/flags.go +++ b/pkg/ccl/cliccl/flags.go @@ -9,11 +9,59 @@ package cliccl import ( + "os" + "strconv" + + "github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl" "github.com/cockroachdb/cockroach/pkg/cli" + "github.com/cockroachdb/cockroach/pkg/cli/clierror" "github.com/cockroachdb/cockroach/pkg/cli/cliflagcfg" "github.com/cockroachdb/cockroach/pkg/cli/cliflags" + "github.com/cockroachdb/cockroach/pkg/cli/exit" + "github.com/cockroachdb/errors" + "github.com/spf13/pflag" ) +type requireFipsFlag bool + +// Type implements the pflag.Value interface. +func (f *requireFipsFlag) Type() string { + return "bool" +} + +// String implements the pflag.Value interface. +func (f *requireFipsFlag) String() string { + return strconv.FormatBool(bool(*f)) +} + +// Set implements the pflag.Value interface. +func (f *requireFipsFlag) Set(s string) error { + v, err := strconv.ParseBool(s) + if err != nil { + return err + } + // We implement the logic of this check in the flag setter itself because it + // applies to all commands and we do not have another good way to inject + // this behavior globally (PersistentPreRun functions don't help because + // they are inherited across different levels of the command hierarchy only + // if that level does not have its own hook). + if v && !fipsccl.IsFIPSReady() { + err := errors.WithHint(errors.New("FIPS readiness checks failed"), "Run `cockroach debug enterprise-check-fips` for details") + clierror.OutputError(os.Stderr, err, true, false) + exit.WithCode(exit.UnspecifiedError()) + } + *f = requireFipsFlag(v) + return nil +} + +var _ pflag.Value = (*requireFipsFlag)(nil) + +// IsBoolFlag implements a non-public pflag interface to indicate that this +// flag is used without an explicit value. +func (*requireFipsFlag) IsBoolFlag() bool { + return true +} + func init() { // Multi-tenancy proxy command flags. { @@ -44,4 +92,12 @@ func init() { // Use StringFlagDepth to avoid conflicting with the already registered KVAddrs env var. cliflagcfg.StringFlagDepth(1, f, &testDirectorySvrContext.kvAddrs, cliflags.KVAddrs) }) + + // FIPS verification flags. + cli.RegisterFlags(func() { + cmd := cli.CockroachCmd() + var requireFips = requireFipsFlag(false) + flag := cmd.PersistentFlags().VarPF(&requireFips, "enterprise-require-fips-ready", "", "abort if FIPS readiness checks fail") + flag.NoOptDefVal = "true" + }) } diff --git a/pkg/ccl/logictestccl/testdata/logic_test/fips_ready b/pkg/ccl/logictestccl/testdata/logic_test/fips_ready new file mode 100644 index 000000000000..be6f4294bfa9 --- /dev/null +++ b/pkg/ccl/logictestccl/testdata/logic_test/fips_ready @@ -0,0 +1,14 @@ +subtest fips_ready + +# We do not have the plumbing that would let test cases know whether they are +# running in a fips environment or not so this is just a very basic test to +# make sure that all the registration, oids, etc work properly. +query _ +SELECT crdb_internal.fips_ready() +---- +_ + +user testuser + +statement error pq: crdb_internal\.fips_ready\(\): user testuser does not have VIEWCLUSTERSETTING system privilege +SELECT crdb_internal.fips_ready() diff --git a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go index ad77a7d68d5e..bdeee99fc27b 100644 --- a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go +++ b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go @@ -2564,6 +2564,13 @@ func TestTenantLogicCCL_crdb_internal_tenant( runCCLLogicTest(t, "crdb_internal_tenant") } +func TestTenantLogicCCL_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestTenantLogicCCL_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel index 79dea2a5ba2b..8cd444649cd6 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 14, + shard_count = 15, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go index be728a4581c4..c590d613629d 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-disk/generated_test.go @@ -78,6 +78,13 @@ func TestLogic_tmp(t *testing.T) { logictest.RunLogicTests(t, logictest.TestServerArgs{}, configIdx, glob) } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel index 1a8f71c8367b..9fa50c2f327b 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 14, + shard_count = 15, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go index 685944ae63a4..0119d51a7a33 100644 --- a/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist-vec-off/generated_test.go @@ -78,6 +78,13 @@ func TestLogic_tmp(t *testing.T) { logictest.RunLogicTests(t, logictest.TestServerArgs{}, configIdx, glob) } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel index dc7145ec9069..70124a288de9 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/fakedist/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 15, + shard_count = 16, tags = [ "ccl_test", "cpu:2", diff --git a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go index c6b81197ee7c..afeb65534e3b 100644 --- a/pkg/ccl/logictestccl/tests/fakedist/generated_test.go +++ b/pkg/ccl/logictestccl/tests/fakedist/generated_test.go @@ -78,6 +78,13 @@ func TestLogic_tmp(t *testing.T) { logictest.RunLogicTests(t, logictest.TestServerArgs{}, configIdx, glob) } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel index a92620762452..98c684b098f1 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 14, + shard_count = 15, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go index 8bba6304a8a7..0b2043469886 100644 --- a/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-legacy-schema-changer/generated_test.go @@ -78,6 +78,13 @@ func TestLogic_tmp(t *testing.T) { logictest.RunLogicTests(t, logictest.TestServerArgs{}, configIdx, glob) } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-mixed-23.1/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-mixed-23.1/BUILD.bazel index 4b784f37a166..6b231b7b6fe3 100644 --- a/pkg/ccl/logictestccl/tests/local-mixed-23.1/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-mixed-23.1/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 8, + shard_count = 9, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-mixed-23.1/generated_test.go b/pkg/ccl/logictestccl/tests/local-mixed-23.1/generated_test.go index c221cd332571..90eeebd019e0 100644 --- a/pkg/ccl/logictestccl/tests/local-mixed-23.1/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-mixed-23.1/generated_test.go @@ -85,6 +85,13 @@ func TestCCLLogic_changefeed( runCCLLogicTest(t, "changefeed") } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel index 7b8c1c305010..b1d932f579d0 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local-vec-off/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 14, + shard_count = 15, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go index 4a072c34ae09..187eb874dfd3 100644 --- a/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local-vec-off/generated_test.go @@ -78,6 +78,13 @@ func TestLogic_tmp(t *testing.T) { logictest.RunLogicTests(t, logictest.TestServerArgs{}, configIdx, glob) } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/logictestccl/tests/local/BUILD.bazel b/pkg/ccl/logictestccl/tests/local/BUILD.bazel index c2b46467aedf..892361db5668 100644 --- a/pkg/ccl/logictestccl/tests/local/BUILD.bazel +++ b/pkg/ccl/logictestccl/tests/local/BUILD.bazel @@ -13,7 +13,7 @@ go_test( "//pkg/ccl/logictestccl:testdata", # keep ], exec_properties = {"Pool": "large"}, - shard_count = 28, + shard_count = 29, tags = [ "ccl_test", "cpu:1", diff --git a/pkg/ccl/logictestccl/tests/local/generated_test.go b/pkg/ccl/logictestccl/tests/local/generated_test.go index 7c04d17a1221..8cc20579db04 100644 --- a/pkg/ccl/logictestccl/tests/local/generated_test.go +++ b/pkg/ccl/logictestccl/tests/local/generated_test.go @@ -120,6 +120,13 @@ func TestCCLLogic_explain_redact( runCCLLogicTest(t, "explain_redact") } +func TestCCLLogic_fips_ready( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runCCLLogicTest(t, "fips_ready") +} + func TestCCLLogic_new_schema_changer( t *testing.T, ) { diff --git a/pkg/ccl/securityccl/fipsccl/BUILD.bazel b/pkg/ccl/securityccl/fipsccl/BUILD.bazel new file mode 100644 index 000000000000..c71c275b068e --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/BUILD.bazel @@ -0,0 +1,26 @@ +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", + "sql.go", + ], + cgo = True, + importpath = "github.com/cockroachdb/cockroach/pkg/ccl/securityccl/fipsccl", + visibility = ["//visibility:public"], + deps = [ + "//pkg/ccl/utilccl", + "//pkg/sql/privilege", + "//pkg/sql/roleoption", + "//pkg/sql/sem/eval", + "//pkg/sql/sem/tree", + "//pkg/sql/sem/volatility", + "//pkg/sql/syntheticprivilege", + "//pkg/sql/types", + "@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") +} diff --git a/pkg/ccl/securityccl/fipsccl/sql.go b/pkg/ccl/securityccl/fipsccl/sql.go new file mode 100644 index 000000000000..5b76ca4c53ad --- /dev/null +++ b/pkg/ccl/securityccl/fipsccl/sql.go @@ -0,0 +1,64 @@ +// 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 ( + "context" + + "github.com/cockroachdb/cockroach/pkg/ccl/utilccl" + "github.com/cockroachdb/cockroach/pkg/sql/privilege" + "github.com/cockroachdb/cockroach/pkg/sql/roleoption" + "github.com/cockroachdb/cockroach/pkg/sql/sem/eval" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/sql/sem/volatility" + "github.com/cockroachdb/cockroach/pkg/sql/syntheticprivilege" + "github.com/cockroachdb/cockroach/pkg/sql/types" +) + +func init() { + overload := tree.Overload{ + Types: tree.ParamTypes{}, + ReturnType: tree.FixedReturnType(types.Bool), + Fn: func(ctx context.Context, evalCtx *eval.Context, args tree.Datums) (tree.Datum, error) { + if err := utilccl.CheckEnterpriseEnabled( + evalCtx.Settings, evalCtx.ClusterID, "fips_ready", + ); err != nil { + return nil, err + } + // It's debatable whether we need a permission check here at all. + // It's not very sensitive and is (currently) a very cheap function + // call. However, it's something that regular users should have no + // reason to look at so in the interest of least privilege we put it + // behind the VIEWCLUSTERSETTING privilige. + session := evalCtx.SessionAccessor + isAdmin, err := session.HasAdminRole(ctx) + if err != nil { + return nil, err + } + if !isAdmin { + hasView, err := session.HasRoleOption(ctx, roleoption.VIEWCLUSTERSETTING) + if err != nil { + return nil, err + } + if !hasView { + if err := session.CheckPrivilege(ctx, syntheticprivilege.GlobalPrivilegeObject, privilege.VIEWCLUSTERSETTING); err != nil { + return nil, err + } + } + } + return tree.MakeDBool(tree.DBool(IsFIPSReady())), nil + }, + Class: tree.NormalClass, + Volatility: volatility.Stable, + } + + utilccl.RegisterCCLBuiltin("crdb_internal.fips_ready", + `Returns true if all FIPS readiness checks pass.`, + overload) +} diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 7beb18e35f4a..ec1ff3f14f79 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -228,6 +228,11 @@ var cockroachCmd = &cobra.Command{ }, } +// CockroachCmd returns the root cockroach Command object. +func CockroachCmd() *cobra.Command { + return cockroachCmd +} + var workloadCmd = workloadcli.WorkloadCmd(true /* userFacing */) func init() { diff --git a/pkg/sql/sem/builtins/fixed_oids.go b/pkg/sql/sem/builtins/fixed_oids.go index 665ec8f710ed..2caadd857117 100644 --- a/pkg/sql/sem/builtins/fixed_oids.go +++ b/pkg/sql/sem/builtins/fixed_oids.go @@ -2506,6 +2506,7 @@ var builtinOidsArray = []string{ 2535: `last_value(val: refcursor) -> refcursor`, 2536: `percentile_disc_impl(arg1: float, arg2: refcursor) -> refcursor`, 2537: `percentile_disc_impl(arg1: float[], arg2: refcursor) -> refcursor[]`, + 2543: `crdb_internal.fips_ready() -> bool`, } var builtinOidsBySignature map[string]oid.Oid