github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/gssapiccl/gssapi.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 // We use a non-standard build tag here because we want to only build on 10 // linux-gnu targets (i.e., not musl). Since go doesn't have a builtin way 11 // to do that, we have to set this in the top-level Makefile. 12 13 // +build gss 14 15 package gssapiccl 16 17 import ( 18 "context" 19 "crypto/tls" 20 "strings" 21 "unsafe" 22 23 "github.com/cockroachdb/cockroach/pkg/ccl/utilccl" 24 "github.com/cockroachdb/cockroach/pkg/clusterversion" 25 "github.com/cockroachdb/cockroach/pkg/security" 26 "github.com/cockroachdb/cockroach/pkg/sql" 27 "github.com/cockroachdb/cockroach/pkg/sql/pgwire" 28 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/hba" 29 "github.com/cockroachdb/errors" 30 ) 31 32 // #cgo LDFLAGS: -lgssapi_krb5 -lcom_err -lkrb5 -lkrb5support -ldl -lk5crypto -lresolv 33 // 34 // #include <gssapi/gssapi.h> 35 // #include <stdlib.h> 36 import "C" 37 38 const ( 39 authTypeGSS int32 = 7 40 authTypeGSSContinue int32 = 8 41 ) 42 43 // authGSS performs GSS authentication. See: 44 // https://github.com/postgres/postgres/blob/0f9cdd7dca694d487ab663d463b308919f591c02/src/backend/libpq/auth.c#L1090 45 func authGSS( 46 ctx context.Context, 47 c pgwire.AuthConn, 48 tlsState tls.ConnectionState, 49 _ pgwire.PasswordRetrievalFn, 50 _ pgwire.PasswordValidUntilFn, 51 execCfg *sql.ExecutorConfig, 52 entry *hba.Entry, 53 ) (security.UserAuthHook, error) { 54 return func(requestedUser string, clientConnection bool) (func(), error) { 55 var ( 56 majStat, minStat, lminS, gflags C.OM_uint32 57 gbuf C.gss_buffer_desc 58 contextHandle C.gss_ctx_id_t = C.GSS_C_NO_CONTEXT 59 acceptorCredHandle C.gss_cred_id_t = C.GSS_C_NO_CREDENTIAL 60 srcName C.gss_name_t 61 outputToken C.gss_buffer_desc 62 63 token []byte 64 err error 65 ) 66 67 if err = c.SendAuthRequest(authTypeGSS, nil); err != nil { 68 return nil, err 69 } 70 71 // This cleanup function must be called at the 72 // "completion of a communications session", not 73 // merely at the end of an authentication init. See 74 // https://tools.ietf.org/html/rfc2744.html, section 75 // `1. Introduction`, stage `d`: 76 // 77 // At the completion of a communications session (which 78 // may extend across several transport connections), 79 // each application calls a GSS-API routine to delete 80 // the security context. 81 // 82 // See https://github.com/postgres/postgres/blob/f4d59369d2ddf0ad7850112752ec42fd115825d4/src/backend/libpq/pqcomm.c#L269 83 connClose := func() { 84 C.gss_delete_sec_context(&lminS, &contextHandle, C.GSS_C_NO_BUFFER) 85 } 86 87 for { 88 token, err = c.GetPwdData() 89 if err != nil { 90 return connClose, err 91 } 92 93 gbuf.length = C.ulong(len(token)) 94 gbuf.value = C.CBytes([]byte(token)) 95 96 majStat = C.gss_accept_sec_context( 97 &minStat, 98 &contextHandle, 99 acceptorCredHandle, 100 &gbuf, 101 C.GSS_C_NO_CHANNEL_BINDINGS, 102 &srcName, 103 nil, 104 &outputToken, 105 &gflags, 106 nil, 107 nil, 108 ) 109 C.free(unsafe.Pointer(gbuf.value)) 110 111 if outputToken.length != 0 { 112 outputBytes := C.GoBytes(outputToken.value, C.int(outputToken.length)) 113 C.gss_release_buffer(&lminS, &outputToken) 114 if err = c.SendAuthRequest(authTypeGSSContinue, outputBytes); err != nil { 115 return connClose, err 116 } 117 } 118 if majStat != C.GSS_S_COMPLETE && majStat != C.GSS_S_CONTINUE_NEEDED { 119 return connClose, gssError("accepting GSS security context failed", majStat, minStat) 120 } 121 if majStat != C.GSS_S_CONTINUE_NEEDED { 122 break 123 } 124 } 125 126 majStat = C.gss_display_name(&minStat, srcName, &gbuf, nil) 127 if majStat != C.GSS_S_COMPLETE { 128 return connClose, gssError("retrieving GSS user name failed", majStat, minStat) 129 } 130 gssUser := C.GoStringN((*C.char)(gbuf.value), C.int(gbuf.length)) 131 C.gss_release_buffer(&lminS, &gbuf) 132 133 realms := entry.GetOptions("krb_realm") 134 135 if idx := strings.IndexByte(gssUser, '@'); idx >= 0 { 136 if len(realms) > 0 { 137 realm := gssUser[idx+1:] 138 matched := false 139 for _, krbRealm := range realms { 140 if realm == krbRealm { 141 matched = true 142 break 143 } 144 } 145 if !matched { 146 return connClose, errors.Errorf("GSSAPI realm (%s) didn't match any configured realm", realm) 147 } 148 } 149 if entry.GetOption("include_realm") != "1" { 150 gssUser = gssUser[:idx] 151 } 152 } else if len(realms) > 0 { 153 return connClose, errors.New("GSSAPI did not return realm but realm matching was requested") 154 } 155 156 if !strings.EqualFold(gssUser, requestedUser) { 157 return connClose, errors.Errorf("requested user is %s, but GSSAPI auth is for %s", requestedUser, gssUser) 158 } 159 160 // Do the license check last so that administrators are able to test whether 161 // their GSS configuration is correct. That is, the presence of this error 162 // message means they have a correctly functioning GSS/Kerberos setup, 163 // but now need to enable enterprise features. 164 return connClose, utilccl.CheckEnterpriseEnabled(execCfg.Settings, execCfg.ClusterID(), execCfg.Organization(), "GSS authentication") 165 }, nil 166 } 167 168 func gssError(msg string, majStat, minStat C.OM_uint32) error { 169 var ( 170 gmsg C.gss_buffer_desc 171 lminS, msgCtx C.OM_uint32 172 ) 173 174 msgCtx = 0 175 C.gss_display_status(&lminS, majStat, C.GSS_C_GSS_CODE, C.GSS_C_NO_OID, &msgCtx, &gmsg) 176 msgMajor := C.GoString((*C.char)(gmsg.value)) 177 C.gss_release_buffer(&lminS, &gmsg) 178 179 msgCtx = 0 180 C.gss_display_status(&lminS, minStat, C.GSS_C_MECH_CODE, C.GSS_C_NO_OID, &msgCtx, &gmsg) 181 msgMinor := C.GoString((*C.char)(gmsg.value)) 182 C.gss_release_buffer(&lminS, &gmsg) 183 184 return errors.Errorf("%s: %s: %s", msg, msgMajor, msgMinor) 185 } 186 187 func checkEntry(entry hba.Entry) error { 188 hasInclude0 := false 189 for _, op := range entry.Options { 190 switch op[0] { 191 case "include_realm": 192 if op[1] == "0" { 193 hasInclude0 = true 194 } else { 195 return errors.Errorf("include_realm must be set to 0: %s", op[1]) 196 } 197 case "krb_realm": 198 default: 199 return errors.Errorf("unsupported option %s", op[0]) 200 } 201 } 202 if !hasInclude0 { 203 return errors.New(`missing "include_realm=0" option in GSS entry`) 204 } 205 return nil 206 } 207 208 func init() { 209 pgwire.RegisterAuthMethod("gss", authGSS, clusterversion.Version19_1, hba.ConnHostSSL, checkEntry) 210 }