Skip to content

Commit

Permalink
Merge pull request #74459 from bobvawter/backport21.2-70269
Browse files Browse the repository at this point in the history
release-21.2: pgwire: Add dynamic user identity mapping
  • Loading branch information
bobvawter committed Jan 5, 2022
2 parents 14bcb15 + 26fd1f6 commit 1c4c429
Show file tree
Hide file tree
Showing 29 changed files with 1,691 additions and 369 deletions.
12 changes: 8 additions & 4 deletions docs/generated/eventlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1740,7 +1740,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 @@ -1767,7 +1768,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 @@ -1793,7 +1795,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 @@ -1866,7 +1869,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 @@ -50,6 +50,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 (this feature is experimental)
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) (this feature is experimental)
server.oidc_authentication.claim_json_key string sets JSON key of principal to extract from payload after OIDC authentication completes (usually email or sid) (this feature is experimental)
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 @@ -55,6 +55,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 (this feature is experimental)</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) (this feature is experimental)</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) (this feature is experimental)</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 @@ -272,6 +272,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 @@ -50,6 +50,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 @@ -59,9 +61,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 @@ -93,6 +99,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 @@ -102,6 +138,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 1c4c429

Please sign in to comment.