Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release-22.1: cert: Add CLI to create tenant scope client cert #84313

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/acceptance/cluster/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ go_library(
deps = [
"//pkg/base",
"//pkg/config/zonepb",
"//pkg/roachpb",
"//pkg/security",
"//pkg/util/contextutil",
"//pkg/util/log",
Expand Down
10 changes: 8 additions & 2 deletions pkg/acceptance/cluster/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"path/filepath"
"time"

"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security"
)

Expand Down Expand Up @@ -55,14 +56,19 @@ func GenerateCerts(ctx context.Context) func() {
keyLen, 96*time.Hour, false, false))

// Root user.
// Scope root user to system tenant and tenant ID 5 which is what we use by default for acceptance
// tests.
userScopes := []roachpb.TenantID{roachpb.SystemTenantID, roachpb.MakeTenantID(5)}
maybePanic(security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedCAKey),
keyLen, 48*time.Hour, false, security.RootUserName(), true /* generate pk8 key */))
keyLen, 48*time.Hour, false, security.RootUserName(), userScopes, true /* generate pk8 key */))

// Test user.
// Scope test user to system tenant and tenant ID 5 which is what we use by default for acceptance
// tests.
maybePanic(security.CreateClientPair(
certsDir, filepath.Join(certsDir, security.EmbeddedCAKey),
keyLen, 48*time.Hour, false, security.TestUserName(), true /* generate pk8 key */))
keyLen, 48*time.Hour, false, security.TestUserName(), userScopes, true /* generate pk8 key */))

// Certs for starting a cockroach server. Key size is from cli/cert.go:defaultKeySize.
maybePanic(security.CreateNodePair(
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func runCreateClientCert(cmd *cobra.Command, args []string) error {
certCtx.certificateLifetime,
certCtx.overwriteFiles,
username,
certCtx.tenantScope,
certCtx.generatePKCS8Key),
"failed to generate client certificate and key")
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/cli/cliflags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,14 @@ Note: that --external-io-disable-http or --external-io-disable-implicit-credenti
Description: `Certificate and key files are overwritten if they exist.`,
}

TenantScope = FlagInfo{
Name: "tenant-scope",
Description: `Assign a tenant scope to the certificate.
This will allow for the certificate to only be used specifically for a particular
tenant. This flag is optional, when omitted, the certificate is scoped to the
system tenant.`,
}

GeneratePKCS8Key = FlagInfo{
Name: "also-generate-pkcs8-key",
Description: `Also write the key in pkcs8 format to <certs-dir>/client.<username>.key.pk8.`,
Expand Down
6 changes: 6 additions & 0 deletions pkg/cli/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/cli/clisqlshell"
"github.com/cockroachdb/cockroach/pkg/cli/democluster"
"github.com/cockroachdb/cockroach/pkg/config/zonepb"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/security"
"github.com/cockroachdb/cockroach/pkg/server"
"github.com/cockroachdb/cockroach/pkg/server/pgurl"
Expand Down Expand Up @@ -271,6 +272,10 @@ var certCtx struct {
// This configuration flag is only used for 'cert' commands
// that generate certificates.
certPrincipalMap []string
// tenantScope indicates a tenantID(s) that a certificate is being
// scoped to. By creating a tenant-scoped certicate, the usage of that certificate
// is restricted to a specific tenant.
tenantScope []roachpb.TenantID
}

func setCertContextDefaults() {
Expand All @@ -283,6 +288,7 @@ func setCertContextDefaults() {
certCtx.overwriteFiles = false
certCtx.generatePKCS8Key = false
certCtx.certPrincipalMap = nil
certCtx.tenantScope = []roachpb.TenantID{roachpb.SystemTenantID}
}

var sqlExecCtx = clisqlexec.Context{
Expand Down
34 changes: 20 additions & 14 deletions pkg/cli/democluster/demo_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -1066,19 +1066,9 @@ func (demoCtx *Context) generateCerts(certsDir string) (err error) {
); err != nil {
return err
}
// Create a certificate for the root user.
if err := security.CreateClientPair(
certsDir,
caKeyPath,
demoCtx.DefaultKeySize,
demoCtx.DefaultCertLifetime,
false, /* overwrite */
security.RootUserName(),
false, /* generatePKCS8Key */
); err != nil {
return err
}

// rootUserScope contains the tenant IDs the root user is allowed to access.
rootUserScope := []roachpb.TenantID{roachpb.SystemTenantID}
if demoCtx.Multitenant {
tenantCAKeyPath := filepath.Join(certsDir, security.EmbeddedTenantCAKey)
// Create a CA key for the tenants.
Expand All @@ -1104,12 +1094,13 @@ func (demoCtx *Context) generateCerts(certsDir string) (err error) {
"localhost",
"*.local",
}
tenantID := uint64(i + 2)
pair, err := security.CreateTenantPair(
certsDir,
tenantCAKeyPath,
demoCtx.DefaultKeySize,
demoCtx.DefaultCertLifetime,
uint64(i+2),
tenantID,
hostAddrs,
)
if err != nil {
Expand All @@ -1119,12 +1110,27 @@ func (demoCtx *Context) generateCerts(certsDir string) (err error) {
return err
}
if err := security.CreateTenantSigningPair(
certsDir, demoCtx.DefaultCertLifetime, false /* overwrite */, uint64(i+2),
certsDir, demoCtx.DefaultCertLifetime, false /* overwrite */, tenantID,
); err != nil {
return err
}
rootUserScope = append(rootUserScope, roachpb.MakeTenantID(tenantID))
}
}
// Create a certificate for the root user. This certificate will be scoped to the
// system tenant and all other tenants created as a part of the demo.
if err := security.CreateClientPair(
certsDir,
caKeyPath,
demoCtx.DefaultKeySize,
demoCtx.DefaultCertLifetime,
false, /* overwrite */
security.RootUserName(),
rootUserScope,
false, /* generatePKCS8Key */
); err != nil {
return err
}
return nil
}

Expand Down
37 changes: 37 additions & 0 deletions pkg/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,42 @@ func (a clusterNameSetter) String() string { return *a.clusterName }
// Type implements the pflag.Value interface.
func (a clusterNameSetter) Type() string { return "<identifier>" }

// tenantIDSetter wraps a list of roachpb.TenantIDs and enables setting them via a command-line flag.
type tenantIDSetter struct {
tenantIDs *[]roachpb.TenantID
}

// String implements the pflag.Value interface.
func (t tenantIDSetter) String() string {
var tenantString strings.Builder
separator := ""
for _, tID := range *t.tenantIDs {
tenantString.WriteString(separator)
tenantString.WriteString(strconv.FormatUint(tID.ToUint64(), 10))
separator = ","
}
return tenantString.String()
}

// Type implements the pflag.Value interface.
func (t tenantIDSetter) Type() string { return "<[]TenantID>" }

// Set implements the pflag.Value interface.
func (t tenantIDSetter) Set(v string) error {
// Reset tenantIDs slice as it is initialized to contain the system tenant ID
// by default.
*t.tenantIDs = []roachpb.TenantID{}
tenantScopes := strings.Split(v, "," /* separator */)
for _, tenantScope := range tenantScopes {
tenantID, err := roachpb.TenantIDFromString(tenantScope)
if err != nil {
return err
}
*t.tenantIDs = append(*t.tenantIDs, tenantID)
}
return nil
}

// Set implements the pflag.Value interface.
func (a clusterNameSetter) Set(v string) error {
if v == "" {
Expand Down Expand Up @@ -588,6 +624,7 @@ func init() {
stringFlag(f, &certCtx.caKey, cliflags.CAKey)
intFlag(f, &certCtx.keySize, cliflags.KeySize)
boolFlag(f, &certCtx.overwriteFiles, cliflags.OverwriteFiles)
varFlag(f, &tenantIDSetter{tenantIDs: &certCtx.tenantScope}, cliflags.TenantScope)

if strings.HasSuffix(cmd.Name(), "-ca") {
// CA-only commands.
Expand Down
4 changes: 4 additions & 0 deletions pkg/cmd/roachtest/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,10 @@ func (c *clusterImpl) IsLocal() bool {
return c.name == "local"
}

func (c *clusterImpl) IsSecure() bool {
return c.localCertsDir != ""
}

// Extend extends the cluster's expiration by d.
func (c *clusterImpl) Extend(ctx context.Context, d time.Duration, l *logger.Logger) error {
if ctx.Err() != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/roachtest/cluster/cluster_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type Cluster interface {
Spec() spec.ClusterSpec
Name() string
IsLocal() bool
IsSecure() bool

// Deleting CockroachDB data and logs on nodes.

Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/roachtest/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ go_library(
"mixed_version_schemachange.go",
"multitenant.go",
"multitenant_upgrade.go",
"multitenant_utils.go",
"network.go",
"nodejs_postgres.go",
"orm_helpers.go",
Expand Down Expand Up @@ -165,8 +166,10 @@ go_library(
"//pkg/jobs/jobspb",
"//pkg/kv",
"//pkg/roachpb",
"//pkg/roachprod",
"//pkg/roachprod/install",
"//pkg/roachprod/logger",
"//pkg/security",
"//pkg/server",
"//pkg/server/serverpb",
"//pkg/sql/pgwire/pgcode",
Expand Down
5 changes: 1 addition & 4 deletions pkg/cmd/roachtest/tests/multitenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,12 @@ func runAcceptanceMultitenant(ctx context.Context, t test.Test, c cluster.Cluste
require.NoError(t, err)
}

kvAddrs, err := c.ExternalAddr(ctx, t.L(), c.All())
require.NoError(t, err)

const (
tenantHTTPPort = 8081
tenantSQLPort = 30258
)
const tenantNode = 1
tenant := createTenantNode(kvAddrs, tenantID, tenantNode, tenantHTTPPort, tenantSQLPort)
tenant := createTenantNode(ctx, t, c, c.All(), tenantID, tenantNode, tenantHTTPPort, tenantSQLPort)
tenant.start(ctx, t, c, "./cockroach")

t.Status("checking that a client can connect to the tenant server")
Expand Down
Loading