Nicolas Massé 5 years ago
parent
commit
07bdb108a3
  1. 2
      README.md
  2. 4
      config/example.yaml
  3. 3
      go.mod
  4. 8
      go.sum
  5. 5
      internal/handlers/auth.go
  6. 102
      internal/handlers/auth/generic_oidc.go
  7. 3
      internal/util/config.go
  8. 6
      web/src/index.js

2
README.md

@ -15,7 +15,7 @@
- Expirable Links - Expirable Links
- URL deletion - URL deletion
- Multiple authorization strategies: - Multiple authorization strategies:
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta) - Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta, or generic OIDC like Keycloak)
- Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/) - Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/)
- Easy [ShareX](https://github.com/ShareX/ShareX) integration - Easy [ShareX](https://github.com/ShareX/ShareX) integration
- Dockerizable - Dockerizable

4
config/example.yaml

@ -22,6 +22,10 @@ Okta: # only relevant when using the oauth authbackend
ClientID: replace me ClientID: replace me
ClientSecret: 'replace me' ClientSecret: 'replace me'
EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta
GenericOIDC: # only relevant when using the oauth authbackend
ClientID: replace me
ClientSecret: 'replace me'
EndpointURL: # (MANDATORY) Base URL, which will be auto-discovered with '.well-known/openid-configuration'
Proxy: # only relevant when using the proxy authbackend Proxy: # only relevant when using the proxy authbackend
RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set
UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header

3
go.mod

@ -5,6 +5,7 @@ go 1.15
require ( require (
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/boltdb/bolt v1.3.1 github.com/boltdb/bolt v1.3.1
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/sessions v0.0.3 github.com/gin-contrib/sessions v0.0.3
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
@ -15,9 +16,11 @@ require (
github.com/mxschmitt/golang-env-struct v0.0.0-20181017075525-0c54aeca8397 github.com/mxschmitt/golang-env-struct v0.0.0-20181017075525-0c54aeca8397
github.com/pborman/uuid v1.2.1 github.com/pborman/uuid v1.2.1
github.com/pkg/errors v0.8.0 github.com/pkg/errors v0.8.0
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
) )

8
go.sum

@ -19,6 +19,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@ -148,6 +150,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -184,6 +188,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@ -259,10 +264,13 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

5
internal/handlers/auth.go

@ -46,6 +46,11 @@ func (h *Handler) initOAuth() {
auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta")) auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta"))
h.providers = append(h.providers, "okta") h.providers = append(h.providers, "okta")
} }
genericOIDC := util.GetConfig().GenericOIDC
if genericOIDC.Enabled() {
auth.WithAdapterWrapper(auth.NewGenericOIDCAdapter(genericOIDC.ClientID, genericOIDC.ClientSecret, genericOIDC.EndpointURL), h.engine.Group("/api/v1/auth/generic_oidc"))
h.providers = append(h.providers, "generic_oidc")
}
h.engine.POST("/api/v1/auth/check", h.handleAuthCheck) h.engine.POST("/api/v1/auth/check", h.handleAuthCheck)
} }

102
internal/handlers/auth/generic_oidc.go

@ -0,0 +1,102 @@
package auth
import (
"context"
"strings"
"github.com/mxschmitt/golang-url-shortener/internal/util"
"github.com/sirupsen/logrus"
oidc "github.com/coreos/go-oidc"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
type genericOIDCAdapter struct {
config *oauth2.Config
oidc *oidc.Config
provider *oidc.Provider
}
type claims struct {
PreferredUsername string `json:"sub"`
Name string `json:"name"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
ACR string `json:"acr"`
}
// NewGenericOIDCAdapter creates an oAuth adapter out of the credentials and the baseURL
func NewGenericOIDCAdapter(clientID, clientSecret, endpointURL string) Adapter {
endpointURL = strings.TrimSuffix(endpointURL, "/")
if endpointURL == "" {
logrus.Error("Configure GenericOIDC Endpoint")
}
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, endpointURL)
if err != nil {
logrus.Error("Configure GenericOIDC Endpoint: " + err.Error())
}
redirectURL := util.GetConfig().BaseURL + "/api/v1/auth/generic_oidc/callback"
// Configure an OpenID Connect aware OAuth client.
return &genericOIDCAdapter{
config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{
"profile",
"openid",
"offline_access",
},
},
oidc: &oidc.Config{
ClientID: clientID,
},
provider: provider,
}
}
func (a *genericOIDCAdapter) GetRedirectURL(state string) string {
return a.config.AuthCodeURL(state)
}
func (a *genericOIDCAdapter) GetUserData(state, code string) (*user, error) {
logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
oAuthToken, err := a.config.Exchange(context.Background(), code)
if err != nil {
return nil, errors.Wrap(err, "could not exchange code")
}
rawIDToken, ok := oAuthToken.Extra("id_token").(string)
if !ok {
return nil, errors.Wrap(err, "No id_token field in oauth2 token.")
}
idToken, err := a.provider.Verifier(a.oidc).Verify(context.Background(), rawIDToken)
if err != nil {
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
}
var oUser claims
if err = idToken.Claims(&oUser); err != nil {
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
}
return &user{
ID: string(oUser.PreferredUsername),
Name: oUser.Name,
Picture: util.GetConfig().BaseURL + "/images/generic_oidc_logo.png", // Default GenericOIDC Avatar
}, nil
}
func (a *genericOIDCAdapter) GetOAuthProviderName() string {
return "generic_oidc"
}

3
internal/util/config.go

@ -29,6 +29,7 @@ type Configuration struct {
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"` GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"` Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
Okta oAuthConf `yaml:"Okta" env:"OKTA"` Okta oAuthConf `yaml:"Okta" env:"OKTA"`
GenericOIDC oAuthConf `yaml:"GenericOIDC" env:"GENERIC_OIDC"`
Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"` Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"`
Redis redisConf `yaml:"Redis" env:"REDIS"` Redis redisConf `yaml:"Redis" env:"REDIS"`
} }
@ -47,7 +48,7 @@ type redisConf struct {
type oAuthConf struct { type oAuthConf struct {
ClientID string `yaml:"ClientID" env:"CLIENT_ID"` ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"` ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta and GenericOIDC
} }
type proxyAuthConf struct { type proxyAuthConf struct {

6
web/src/index.js

@ -137,6 +137,12 @@ export default class BaseComponent extends Component {
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}> <Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}>
<Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta <Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta
</Button> </Button>
{info.providers.includes("generic_oidc") && <div className="ui divider"></div>}
</div>}
{info.providers.includes("generic_oidc") && <div>
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "generic_oidc")}>
<Image src='/images/generic_oidc_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with OpenID Connect
</Button>
</div>} </div>}
{info.providers.includes("microsoft") && <div> {info.providers.includes("microsoft") && <div>
<div className="ui divider"></div> <div className="ui divider"></div>

Loading…
Cancel
Save