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

Revert "OIDC-based user authentication" #1132

Merged
merged 1 commit into from
Jan 17, 2025
Merged
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: 0 additions & 1 deletion cmd/server/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ func NewServer(ctx context.Context, conf config.Config, log *logger.Logger) (*Se
RPCAddress: ":" + conf.Server.RPCPort,
HTTPPort: conf.Server.HTTPPort,
BasicAuth: conf.Server.BasicAuth,
OidcAuth: conf.Server.OidcAuth,
DisableHTTPCache: conf.Server.DisableHTTPCache,
Log: log,
Tasks: &server.TaskService{
Expand Down
10 changes: 0 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,6 @@ type BasicCredential struct {
Password string
}

type OidcAuth struct {
ServiceConfigURL string
ClientId string
ClientSecret string
RedirectURL string
RequireScope string
RequireAudience string
}

// RPCClient describes configuration for gRPC clients
type RPCClient struct {
BasicCredential
Expand All @@ -86,7 +77,6 @@ type Server struct {
HTTPPort string
RPCPort string
BasicAuth []BasicCredential
OidcAuth OidcAuth
DisableHTTPCache bool
}

Expand Down
17 changes: 0 additions & 17 deletions config/default-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,6 @@ Server:
# - User: user2
# Password: foobar

# Require Bearer JWT authentication for the server APIs.
# Server won't launch when configuration URL cannot be loaded.
# OidcAuth:
# # URL of the OIDC service configuration (activates OIDC configuration):
# # Example: https://example.org/oidc/.well-knwon/openid-configuration
# ServiceConfigURL:
# # Client ID and secret are sent with the token introspection request
# # (Basic authentication):
# ClientId:
# ClientSecret:
# # The URL where OIDC should redirect after login (keep the path '/login')
# RedirectURL: "http://localhost:8000/login"
# # Optional: if specified, this scope value must be in the token:
# RequireScope:
# # Optional: if specified, this audience value must be in the token:
# RequireAudience:

# Include a "Cache-Control: no-store" HTTP header in Get/List responses
# to prevent caching by intermediary services.
DisableHTTPCache: true
Expand Down
10 changes: 0 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
Expand All @@ -98,13 +97,11 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gammazero/deque v0.2.1 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
Expand All @@ -128,12 +125,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.10 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.1.3 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maruel/panicparse v1.6.2 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand All @@ -158,7 +149,6 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
Expand Down
21 changes: 0 additions & 21 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
Expand Down Expand Up @@ -127,8 +125,6 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LKdiuDWQpjqw=
github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
Expand All @@ -154,8 +150,6 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
Expand Down Expand Up @@ -279,18 +273,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo=
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
Expand Down Expand Up @@ -387,8 +369,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
Expand Down Expand Up @@ -426,7 +406,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
Expand Down
183 changes: 56 additions & 127 deletions server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package server

import (
"encoding/base64"
"net/http"
"strings"

"github.com/ohsu-comp-bio/funnel/config"
Expand All @@ -13,145 +12,75 @@ import (
"google.golang.org/grpc/status"
)

type Authentication struct {
basic map[string]string
oidc *OidcConfig
}

// Extracted info about the current user, which is exposed through Context.
type UserInfo struct {
// Public users are non-authenticated, in case Funnel configuration does
// not require OIDC nor Basic authentication.
IsPublic bool
// Username of an authenticated user (subject field from JWT).
Username string
// In case of OIDC authentication, the provided Bearer token, which can be
// used when requesting task input data.
Token string
}

// Context key type for storing UserInfo.
// Note: UserInfo is not in the context when the system internally requests data.
type userInfoContextKey string

var (
errMissingMetadata = status.Errorf(codes.InvalidArgument, "Missing metadata in the context")
errTokenRequired = status.Errorf(codes.Unauthenticated, "Basic/Bearer authorization token missing")
errInvalidBasicToken = status.Errorf(codes.Unauthenticated, "Basic-authentication failed")
errInvalidBearerToken = status.Errorf(codes.Unauthenticated, "Bearer authorization token not accepted")
publicUserInfo = UserInfo{IsPublic: true, Username: ""}
UserInfoKey = userInfoContextKey("user-info")
)

func NewAuthentication(creds []config.BasicCredential, oidc config.OidcAuth) *Authentication {
basicCreds := make(map[string]string)

for _, cred := range creds {
credBytes := []byte(cred.User + ":" + cred.Password)
fullValue := "Basic " + base64.StdEncoding.EncodeToString(credBytes)
basicCreds[fullValue] = cred.User
}

return &Authentication{
basic: basicCreds,
oidc: initOidcConfig(oidc),
}
}

// Return a new gRPC interceptor function that authorizes RPCs.
func (a *Authentication) Interceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {

// Case when authentication is not required:
if len(a.basic) == 0 && a.oidc == nil {
ctx = context.WithValue(ctx, UserInfoKey, &publicUserInfo)
return handler(ctx, req)
}

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errMissingMetadata
}

values := md["authorization"]
if len(values) == 0 {
return nil, errTokenRequired
}

authorized := false
authErr := errTokenRequired
authorization := values[0]

if strings.HasPrefix(authorization, "Basic ") {
authErr = errInvalidBasicToken
username := a.basic[authorization]
authorized = username != ""

if authorized {
ctx = context.WithValue(ctx, UserInfoKey, &UserInfo{Username: username})
// Return a new interceptor function that authorizes RPCs
// using a password stored in the config.
func newAuthInterceptor(creds []config.BasicCredential) grpc.UnaryServerInterceptor {

// Return a function that is the interceptor.
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (interface{}, error) {
var authorized bool
var err error
for _, cred := range creds {
err = authorize(ctx, cred.User, cred.Password)
if err == nil {
authorized = true
}
}
} else if a.oidc != nil && strings.HasPrefix(authorization, "Bearer ") {
authErr = errInvalidBearerToken
jwtString := strings.TrimPrefix(authorization, "Bearer ")
subject := a.oidc.ParseJwtSubject(jwtString)
authorized = subject != ""

if authorized {
ctx = context.WithValue(ctx, UserInfoKey, &UserInfo{Username: subject, Token: jwtString})
if len(creds) == 0 {
authorized = true
}
if !authorized {
return nil, err
}
return handler(ctx, req)
}
}

if !authorized {
return nil, authErr
// Check the context's metadata for the configured server/API password.
func authorize(ctx context.Context, user, password string) error {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if len(md["authorization"]) > 0 {
raw := md["authorization"][0]
requser, reqpass, ok := parseBasicAuth(raw)
if ok {
if requser == user && reqpass == password {
return nil
}
return status.Errorf(codes.PermissionDenied, "AUTH DENIED")
}
}
}

return handler(ctx, req)
return status.Errorf(codes.Unauthenticated, "UNAUTHENTICATED")
}

// HTTP request handler for the /login endpoint. Initiates user authentication
// flow based on the configuration (OIDC, Basic, none).
func (a *Authentication) LoginHandler(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.Error(w, "Only GET method is supported.", http.StatusMethodNotAllowed)
}
// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
//
// Taken from Go core: https://golang.org/src/net/http/request.go?s=27379:27445#L828
func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "

if a.oidc != nil {
a.oidc.HandleAuthCode(w, req)
} else if len(a.basic) > 0 {
a.handleBasicAuth(w, req)
} else {
http.Redirect(w, req, "/", http.StatusSeeOther)
if !strings.HasPrefix(auth, prefix) {
return
}
}

// HTTP request handler for the /login/token endpoint. In case of OIDC enabled,
// prints the JWT from the sent cookie. In all other cases, an empty HTTP 200
// response.
func (a *Authentication) EchoTokenHandler(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.Error(w, "Only GET method is supported.", http.StatusMethodNotAllowed)
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])

if a.oidc != nil {
a.oidc.EchoTokenHandler(w, req)
} else {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", "0")
w.WriteHeader(http.StatusOK)
if err != nil {
return
}
}

func (a *Authentication) handleBasicAuth(w http.ResponseWriter, req *http.Request) {
// Check if provided value in the header is valid:
if a.basic[req.Header.Get("Authorization")] == "" {
http.Redirect(w, req, "/", http.StatusSeeOther)
} else {
w.Header().Set("WWW-Authenticate", "Basic realm=Funnel")
msg := "User authentication is required (Basic authentication with " +
"username and password)"
http.Error(w, msg, http.StatusUnauthorized)
cs := string(c)
s := strings.IndexByte(cs, ':')

if s < 0 {
return
}

return cs[:s], cs[s+1:], true
}
Loading
Loading