Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
70269: pgwire: Add dynamic user identity mapping r=bobvawter a=bobvawter

This commit adds support for mapping incoming system usernames (e.g.: GSSAPI
principals, X.509 Common Names) to database usernames. The implementation
follows in the same pattern as the HBA configuration. Namely, there is a new
cluster setting server.identity_map.configuration which contains data
compatible with the pg_ident.conf file.

When a new connection is made, the relevant HBA configuration line is selected
and the "map" option is used to select an identity-map ruleset. The system
username is mapped to a database username and authentication proceeds against
the database username.

The default HBA configuration is updated to add a "map=cockroach-default"
option. This allows users who are solely interested in providing system
username mappings to install an identity-map without also needing to provide an
HBA configuration.

For pedantry's sake, the term "identity" is preferred over "username" within
the code, since not all identities are necessarily what would be considered
usernames.

Fixes: #47196

Release note (security update): The server.identity_map.configuration cluster
setting allows a pg_ident.conf file to be uploaded to support dynamically
remapping system usernames (e.g.: Kerberos principals, X.509 Common Names) to
database usernames.

72774: sql: improve a test r=andreimatei a=andreimatei

This test was deadlocking on t.Fatal(). I've ran into this through #72769.

Release note: None

72878: sem/tree: protect against double panic in EvalCtx.Stop r=andreimatei a=andreimatei

Sometimes in tests EvalCtx.Stop() is called in a defer. When the defer
runs because of a panic, it would likely panic again because its memory
monitor is not empty.

Release note: None

Co-authored-by: Bob Vawter <bob@vawter.org>
Co-authored-by: Andrei Matei <andrei@cockroachlabs.com>
  • Loading branch information
3 people committed Nov 18, 2021
4 parents faacd84 + eaeb574 + d06da0d + bc6109c commit 343c297
Show file tree
Hide file tree
Showing 32 changed files with 1,701 additions and 372 deletions.
12 changes: 8 additions & 4 deletions docs/generated/eventlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1766,7 +1766,8 @@ Events of this type are only emitted when the cluster setting
| `Network` | The network protocol for this connection: tcp4, tcp6, unix, etc. | no |
| `RemoteAddress` | The remote address of the SQL client. Note that when using a proxy or other intermediate server, this field will contain the address of the intermediate server. | yes |
| `Transport` | The connection type after transport negotiation. | no |
| `User` | The username the session is for. This is the username passed by the client, after case-folding and Unicode normalization. | yes |
| `User` | The database username the session is for. This username will have undergone case-folding and Unicode normalization. | yes |
| `SystemIdentity` | The original system identity provided by the client, if an identity mapping was used per Host-Based Authentication rules. This may be a GSSAPI or X.509 principal or any other external value, so no specific assumptions should be made about the contents of this field. | yes |

### `client_authentication_info`

Expand All @@ -1793,7 +1794,8 @@ Events of this type are only emitted when the cluster setting
| `Network` | The network protocol for this connection: tcp4, tcp6, unix, etc. | no |
| `RemoteAddress` | The remote address of the SQL client. Note that when using a proxy or other intermediate server, this field will contain the address of the intermediate server. | yes |
| `Transport` | The connection type after transport negotiation. | no |
| `User` | The username the session is for. This is the username passed by the client, after case-folding and Unicode normalization. | yes |
| `User` | The database username the session is for. This username will have undergone case-folding and Unicode normalization. | yes |
| `SystemIdentity` | The original system identity provided by the client, if an identity mapping was used per Host-Based Authentication rules. This may be a GSSAPI or X.509 principal or any other external value, so no specific assumptions should be made about the contents of this field. | yes |

### `client_authentication_ok`

Expand All @@ -1819,7 +1821,8 @@ Events of this type are only emitted when the cluster setting
| `Network` | The network protocol for this connection: tcp4, tcp6, unix, etc. | no |
| `RemoteAddress` | The remote address of the SQL client. Note that when using a proxy or other intermediate server, this field will contain the address of the intermediate server. | yes |
| `Transport` | The connection type after transport negotiation. | no |
| `User` | The username the session is for. This is the username passed by the client, after case-folding and Unicode normalization. | yes |
| `User` | The database username the session is for. This username will have undergone case-folding and Unicode normalization. | yes |
| `SystemIdentity` | The original system identity provided by the client, if an identity mapping was used per Host-Based Authentication rules. This may be a GSSAPI or X.509 principal or any other external value, so no specific assumptions should be made about the contents of this field. | yes |

### `client_connection_end`

Expand Down Expand Up @@ -1892,7 +1895,8 @@ Events of this type are only emitted when the cluster setting
| `Network` | The network protocol for this connection: tcp4, tcp6, unix, etc. | no |
| `RemoteAddress` | The remote address of the SQL client. Note that when using a proxy or other intermediate server, this field will contain the address of the intermediate server. | yes |
| `Transport` | The connection type after transport negotiation. | no |
| `User` | The username the session is for. This is the username passed by the client, after case-folding and Unicode normalization. | yes |
| `User` | The database username the session is for. This username will have undergone case-folding and Unicode normalization. | yes |
| `SystemIdentity` | The original system identity provided by the client, if an identity mapping was used per Host-Based Authentication rules. This may be a GSSAPI or X.509 principal or any other external value, so no specific assumptions should be made about the contents of this field. | yes |

## SQL Slow Query Log

Expand Down
1 change: 1 addition & 0 deletions docs/generated/settings/settings-for-tenants.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ server.consistency_check.max_rate byte size 8.0 MiB the rate limit (bytes/sec) t
server.eventlog.enabled boolean true if set, logged notable events are also stored in the table system.eventlog
server.eventlog.ttl duration 2160h0m0s if nonzero, entries in system.eventlog older than this duration are deleted every 10m0s. Should not be lowered below 24 hours.
server.host_based_authentication.configuration string host-based authentication configuration to use during connection authentication
server.identity_map.configuration string system-identity to database-username mappings
server.oidc_authentication.autologin boolean false if true, logged-out visitors to the DB Console will be automatically redirected to the OIDC login endpoint
server.oidc_authentication.button_text string Login with your OIDC provider text to show on button on DB Console login page to login with your OIDC provider (only shown if OIDC is enabled)
server.oidc_authentication.claim_json_key string sets JSON key of principal to extract from payload after OIDC authentication completes (usually email or sid)
Expand Down
1 change: 1 addition & 0 deletions docs/generated/settings/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<tr><td><code>server.eventlog.enabled</code></td><td>boolean</td><td><code>true</code></td><td>if set, logged notable events are also stored in the table system.eventlog</td></tr>
<tr><td><code>server.eventlog.ttl</code></td><td>duration</td><td><code>2160h0m0s</code></td><td>if nonzero, entries in system.eventlog older than this duration are deleted every 10m0s. Should not be lowered below 24 hours.</td></tr>
<tr><td><code>server.host_based_authentication.configuration</code></td><td>string</td><td><code></code></td><td>host-based authentication configuration to use during connection authentication</td></tr>
<tr><td><code>server.identity_map.configuration</code></td><td>string</td><td><code></code></td><td>system-identity to database-username mappings</td></tr>
<tr><td><code>server.oidc_authentication.autologin</code></td><td>boolean</td><td><code>false</code></td><td>if true, logged-out visitors to the DB Console will be automatically redirected to the OIDC login endpoint</td></tr>
<tr><td><code>server.oidc_authentication.button_text</code></td><td>string</td><td><code>Login with your OIDC provider</code></td><td>text to show on button on DB Console login page to login with your OIDC provider (only shown if OIDC is enabled)</td></tr>
<tr><td><code>server.oidc_authentication.claim_json_key</code></td><td>string</td><td><code></code></td><td>sets JSON key of principal to extract from payload after OIDC authentication completes (usually email or sid)</td></tr>
Expand Down
1 change: 1 addition & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ ALL_TESTS = [
"//pkg/sql/opt:opt_test",
"//pkg/sql/parser:parser_test",
"//pkg/sql/pgwire/hba:hba_test",
"//pkg/sql/pgwire/identmap:identmap_test",
"//pkg/sql/pgwire/pgerror:pgerror_test",
"//pkg/sql/pgwire:pgwire_test",
"//pkg/sql/physicalplan/replicaoracle:replicaoracle_test",
Expand Down
43 changes: 42 additions & 1 deletion pkg/acceptance/compose/gss/psql/gss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func TestGSS(t *testing.T) {
hbaErr string
// Error message of gss login.
gssErr string
// Optionally inject an HBA identity map.
identMap string
}{
{
conf: `host all all all gss include_realm=0 nope=1`,
Expand All @@ -60,9 +62,13 @@ func TestGSS(t *testing.T) {
conf: `host all all all gss include_realm=1`,
hbaErr: `include_realm must be set to 0`,
},
{
conf: `host all all all gss map=ignored include_realm=1`,
hbaErr: `include_realm must be set to 0`,
},
{
conf: `host all all all gss`,
hbaErr: `missing "include_realm=0"`,
hbaErr: `at least one of "include_realm=0" or "map" options required`,
},
{
conf: `host all all all gss include_realm=0`,
Expand Down Expand Up @@ -94,6 +100,36 @@ func TestGSS(t *testing.T) {
user: "tester",
gssErr: `GSS authentication requires an enterprise license`,
},
// Validate that we can use the "map" option to strip the realm
// data. Note that the system-identity value will have been
// normalized into a lower-case value.
{
conf: `host all all all gss map=demo`,
identMap: `demo /^(.*)@my.ex$ \1`,
user: "tester",
gssErr: `GSS authentication requires an enterprise license`,
},
// Verify case-sensitivity.
{
conf: `host all all all gss map=demo`,
identMap: `demo /^(.*)@MY.EX$ \1`,
user: "tester",
gssErr: `system identity "tester@my.ex" did not map to a database role`,
},
// Validating the use of "map" as a filter.
{
conf: `host all all all gss map=demo`,
identMap: `demo /^(.*)@NOPE.EX$ \1`,
user: "tester",
gssErr: `system identity "tester@my.ex" did not map to a database role`,
},
// Check map+include_realm=0 case.
{
conf: `host all all all gss include_realm=0 map=demo`,
identMap: `demo tester remapped`,
user: "remapped",
gssErr: `GSS authentication requires an enterprise license`,
},
}
for i, tc := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
Expand All @@ -103,6 +139,11 @@ func TestGSS(t *testing.T) {
if tc.hbaErr != "" {
return
}
if tc.identMap != "" {
if _, err := db.Exec(`SET CLUSTER SETTING server.identity_map.configuration = $1`, tc.identMap); err != nil {
t.Fatalf("bad identity_map: %v", err)
}
}
if _, err := db.Exec(fmt.Sprintf(`CREATE USER IF NOT EXISTS %s`, tc.user)); err != nil {
t.Fatal(err)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/ccl/gssapiccl/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "gssapiccl",
srcs = select({
"@io_bazel_rules_go//go/platform:linux_amd64": ["gssapi.go"],
"@io_bazel_rules_go//go/platform:linux_amd64": ["gssapi.go", "get_user.go"],
"//conditions:default": ["empty.go"],
}),
cdeps = select({
Expand All @@ -26,6 +26,7 @@ go_library(
"//pkg/sql/sem/tree",
"//pkg/sql/pgwire",
"//pkg/sql/pgwire/hba",
"//pkg/sql/pgwire/identmap",
"@com_github_cockroachdb_errors//:errors",
],
"//conditions:default": [],
Expand Down
128 changes: 128 additions & 0 deletions pkg/ccl/gssapiccl/get_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2021 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

// See comment on build tag in gssapi.go.

//go:build gss
// +build gss

package gssapiccl

// This file contains the code that calls out to the GSSAPI library
// to retrieve the current user.

import (
"unsafe"

"github.com/cockroachdb/cockroach/pkg/sql/pgwire"
"github.com/cockroachdb/errors"
)

// #cgo LDFLAGS: -lgssapi_krb5 -lcom_err -lkrb5 -lkrb5support -ldl -lk5crypto -lresolv
//
// #include <gssapi/gssapi.h>
// #include <stdlib.h>
import "C"

func getGssUser(c pgwire.AuthConn) (connClose func(), gssUser string, _ error) {
var (
majStat, minStat, lminS, gflags C.OM_uint32
gbuf C.gss_buffer_desc
contextHandle C.gss_ctx_id_t = C.GSS_C_NO_CONTEXT
acceptorCredHandle C.gss_cred_id_t = C.GSS_C_NO_CREDENTIAL
srcName C.gss_name_t
outputToken C.gss_buffer_desc
)

if err := c.SendAuthRequest(authTypeGSS, nil); err != nil {
return nil, "", err
}

// This cleanup function must be called at the
// "completion of a communications session", not
// merely at the end of an authentication init. See
// https://tools.ietf.org/html/rfc2744.html, section
// `1. Introduction`, stage `d`:
//
// At the completion of a communications session (which
// may extend across several transport connections),
// each application calls a GSS-API routine to delete
// the security context.
//
// See https://github.com/postgres/postgres/blob/f4d59369d2ddf0ad7850112752ec42fd115825d4/src/backend/libpq/pqcomm.c#L269
connClose = func() {
C.gss_delete_sec_context(&lminS, &contextHandle, C.GSS_C_NO_BUFFER)
}

for {
token, err := c.GetPwdData()
if err != nil {
return connClose, "", err
}

gbuf.length = C.ulong(len(token))
gbuf.value = C.CBytes([]byte(token))

majStat = C.gss_accept_sec_context(
&minStat,
&contextHandle,
acceptorCredHandle,
&gbuf,
C.GSS_C_NO_CHANNEL_BINDINGS,
&srcName,
nil,
&outputToken,
&gflags,
nil,
nil,
)
C.free(unsafe.Pointer(gbuf.value))

if outputToken.length != 0 {
outputBytes := C.GoBytes(outputToken.value, C.int(outputToken.length))
C.gss_release_buffer(&lminS, &outputToken)
if err := c.SendAuthRequest(authTypeGSSContinue, outputBytes); err != nil {
return connClose, "", err
}
}
if majStat != C.GSS_S_COMPLETE && majStat != C.GSS_S_CONTINUE_NEEDED {
return connClose, "", gssError("accepting GSS security context failed", majStat, minStat)
}
if majStat != C.GSS_S_CONTINUE_NEEDED {
break
}
}

majStat = C.gss_display_name(&minStat, srcName, &gbuf, nil)
if majStat != C.GSS_S_COMPLETE {
return connClose, "", gssError("retrieving GSS user name failed", majStat, minStat)
}
gssUser = C.GoStringN((*C.char)(gbuf.value), C.int(gbuf.length))
C.gss_release_buffer(&lminS, &gbuf)

return connClose, gssUser, nil
}

func gssError(msg string, majStat, minStat C.OM_uint32) error {
var (
gmsg C.gss_buffer_desc
lminS, msgCtx C.OM_uint32
)

msgCtx = 0
C.gss_display_status(&lminS, majStat, C.GSS_C_GSS_CODE, C.GSS_C_NO_OID, &msgCtx, &gmsg)
msgMajor := C.GoString((*C.char)(gmsg.value))
C.gss_release_buffer(&lminS, &gmsg)

msgCtx = 0
C.gss_display_status(&lminS, minStat, C.GSS_C_MECH_CODE, C.GSS_C_NO_OID, &msgCtx, &gmsg)
msgMinor := C.GoString((*C.char)(gmsg.value))
C.gss_release_buffer(&lminS, &gmsg)

return errors.Errorf("%s: %s: %s", msg, msgMajor, msgMinor)
}
Loading

0 comments on commit 343c297

Please sign in to comment.