Compare commits

...

9 Commits

18 changed files with 306 additions and 60 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/bin

7
Makefile Normal file
View File

@ -0,0 +1,7 @@
build: clean
misc/script/build
clean:
rm -rf bin
.PHONY: build

119
conf/hydra-werther.conf Normal file
View File

@ -0,0 +1,119 @@
#WERTHER_DEV_MODE=
# [description] a development mode
# [type] True or False
# [default] false
# [required]
#WERTHER_LISTEN=
# [description] a host and port to listen on (<host>:<port>)
# [type] String
# [default] :8080
# [required]
WERTHER_IDENTP_HYDRA_URL=http://localhost:4445/
# [description] an admin URL of ORY Hydra Server
# [type] String
# [default]
# [required] true
#WERTHER_IDENTP_SESSION_TTL=
# [description] a user session's TTL
# [type] Duration
# [default] 24h
# [required]
#WERTHER_IDENTP_CLAIM_SCOPES=
# [description] a mapping of OpenID Connect claims to scopes (all claims are URL encoded)
# [type] Comma-separated list of String:String pairs
# [default] name:profile,family_name:profile,given_name:profile,email:email,https%3A%2F%2Fgithub.com%2Fi-core%2Fwerther%2Fclaims%2Froles:roles
# [required]
WERTHER_LDAP_ENDPOINTS=localhost:389
# [description] a LDAP's server URLs as "<address>:<port>"
# [type] Comma-separated list of String
# [default]
# [required] true
WERTHER_LDAP_BINDDN=
# [description] a LDAP bind DN
# [type] String
# [default]
# [required]
WERTHER_LDAP_BINDPW=
# [description] a LDAP bind password
# [type] String
# [default]
# [required]
WERTHER_LDAP_BASEDN=ou=users,dc=myorg,dc=com
# [description] a LDAP base DN for searching users
# [type] String
# [default]
# [required] true
#WERTHER_LDAP_USER_SEARCH_QUERY=
# [description] the user search query
# [type] String
# [default] (&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))
# [required]
#WERTHER_LDAP_ATTR_CLAIMS=
# [description] a mapping of LDAP attributes to OpenID connect claims
# [type] Comma-separated list of String:String pairs
# [default] name:name,sn:family_name,givenName:given_name,mail:email
# [required]
WERTHER_LDAP_ROLE_BASEDN=ou=groups,dc=myorg,dc=com
# [description] a LDAP base DN for searching roles
# [type] String
# [default]
# [required] true
#WERTHER_LDAP_ROLE_SEARCH_QUERY=
# [description] the role search query
# [type] String
# [default] (|(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s)))
# [required]
#WERTHER_LDAP_ROLE_ATTR=
# [description] a LDAP group's attribute that contains a role's name
# [type] String
# [default] description
# [required]
#WERTHER_LDAP_ROLE_CLAIM=
# [description] a name of an OpenID Connect claim that contains user roles
# [type] String
# [default] https://github.com/i-core/werther/claims/roles
# [required]
#WERTHER_LDAP_CACHE_SIZE=
# [description] a user info cache's size in KiB
# [type] Integer
# [default] 512
# [required]
#WERTHER_LDAP_CACHE_TTL=
# [description] a user info cache TTL
# [type] Duration
# [default] 30m
# [required]
#WERTHER_LDAP_IS_TLS=
# [description] should LDAP connection be established via TLS
# [type] True or False
# [default] false
# [required]
#WERTHER_WEB_DIR=
# [description] a path to an external web directory
# [type] String
# [default]
# [required]
#WERTHER_WEB_BASE_PATH=
# [description] a base path of web pages
# [type] String
# [default] /
# [required]

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

14
debian/control vendored Normal file
View File

@ -0,0 +1,14 @@
Source: hydra-werther
Section: unknown
Priority: optional
Maintainer: Cadoles <contact@cadoles.com>
Build-Depends: debhelper (>= 8.0.0), wget, ca-certificates, tar
Standards-Version: 3.9.4
Homepage: http://forge.cadoles.com/Cadoles/hydra-werther
Vcs-Git: http://forge.cadoles.com/Cadoles/hydra-werther.git
Vcs-Browser: http://forge.cadoles.com/Cadoles/hydra-werther
Package: hydra-werther
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}, ssl-cert
Description: Hydra identity provider backed by LDAP compatible server

12
debian/hydra-werther.service vendored Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=Run Hydra Werther login/consent/logout app
After=network-online.target
[Service]
Type=simple
EnvironmentFile=/etc/hydra-werther/hydra-werther.conf
ExecStart=/usr/bin/hydra-werther
Restart=on-failure
[Install]
WantedBy=multi-user.target

40
debian/rules vendored Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
GO_VERSION := 1.17.1
OS := linux
ARCH := amd64
GOPATH=$(HOME)/go
ifeq (, $(shell which go 2>/dev/null))
override_dh_auto_build: install-go
endif
%:
dh $@ --with systemd
install-go:
wget https://dl.google.com/go/go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
tar -C /usr/local -xzf go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
override_dh_auto_build: $(GOPATH)
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" DISTS=$(OS)/$(ARCH) make
$(GOPATH):
mkdir -p $(GOPATH)
override_dh_auto_install:
mkdir -p debian/hydra-werther/usr/bin
mkdir -p debian/hydra-werther/etc/hydra-werther
cp bin/werther_$(OS)_$(ARCH) debian/hydra-werther/usr/bin/hydra-werther
cp conf/hydra-werther.conf debian/hydra-werther/etc/hydra-werther
install -d debian/hydra-werther
override_dh_strip:
override_dh_auto_test:

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (native)

View File

@ -14,17 +14,18 @@ import (
// ConsentReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process. // ConsentReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process.
type ConsentReqDoer struct { type ConsentReqDoer struct {
hydraURL string hydraURL string
fakeTLSTermination bool
rememberFor int rememberFor int
} }
// NewConsentReqDoer creates a ConsentRequest. // NewConsentReqDoer creates a ConsentRequest.
func NewConsentReqDoer(hydraURL string, rememberFor int) *ConsentReqDoer { func NewConsentReqDoer(hydraURL string, fakeTLSTermination bool, rememberFor int) *ConsentReqDoer {
return &ConsentReqDoer{hydraURL: hydraURL, rememberFor: rememberFor} return &ConsentReqDoer{hydraURL: hydraURL, fakeTLSTermination: fakeTLSTermination, rememberFor: rememberFor}
} }
// InitiateRequest fetches information on the OAuth2 request. // InitiateRequest fetches information on the OAuth2 request.
func (crd *ConsentReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) { func (crd *ConsentReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
ri, err := initiateRequest(consent, crd.hydraURL, challenge) ri, err := initiateRequest(consent, crd.hydraURL, crd.fakeTLSTermination, challenge)
return ri, errors.Wrap(err, "failed to initiate consent request") return ri, errors.Wrap(err, "failed to initiate consent request")
} }
@ -46,6 +47,6 @@ func (crd *ConsentReqDoer) AcceptConsentRequest(challenge string, remember bool,
IDToken: idToken, IDToken: idToken,
}, },
} }
redirectURI, err := acceptRequest(consent, crd.hydraURL, challenge, data) redirectURI, err := acceptRequest(consent, crd.hydraURL, crd.fakeTLSTermination, challenge, data)
return redirectURI, errors.Wrap(err, "failed to accept consent request") return redirectURI, errors.Wrap(err, "failed to accept consent request")
} }

View File

@ -55,7 +55,7 @@ func TestInitiateConsentRequest(t *testing.T) {
h := &testInitiateConsentHandler{reqInfo: tc.reqInfo, status: tc.status} h := &testInitiateConsentHandler{reqInfo: tc.reqInfo, status: tc.status}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewConsentReqDoer(srv.URL, tc.rememberFor) ldr := hydra.NewConsentReqDoer(srv.URL, false, tc.rememberFor)
reqInfo, err := ldr.InitiateRequest(tc.challenge) reqInfo, err := ldr.InitiateRequest(tc.challenge)
@ -149,7 +149,7 @@ func TestAcceptConsentRequest(t *testing.T) {
h := &testAcceptConsentHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect} h := &testAcceptConsentHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewConsentReqDoer(srv.URL, tc.rememberFor) ldr := hydra.NewConsentReqDoer(srv.URL, false, tc.rememberFor)
var grantScope []string var grantScope []string
for _, v := range tc.grantScope { for _, v := range tc.grantScope {

View File

@ -44,7 +44,7 @@ type ReqInfo struct {
Subject string `json:"subject"` Subject string `json:"subject"`
} }
func initiateRequest(typ reqType, hydraURL, challenge string) (*ReqInfo, error) { func initiateRequest(typ reqType, hydraURL string, fakeTLSTermination bool, challenge string) (*ReqInfo, error) {
if challenge == "" { if challenge == "" {
return nil, ErrChallengeMissed return nil, ErrChallengeMissed
} }
@ -58,7 +58,16 @@ func initiateRequest(typ reqType, hydraURL, challenge string) (*ReqInfo, error)
} }
u = u.ResolveReference(ref) u = u.ResolveReference(ref)
resp, err := http.Get(u.String()) req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
if fakeTLSTermination {
req.Header.Add("X-Forwarded-Proto", "https")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +85,7 @@ func initiateRequest(typ reqType, hydraURL, challenge string) (*ReqInfo, error)
return &ri, nil return &ri, nil
} }
func acceptRequest(typ reqType, hydraURL, challenge string, data interface{}) (string, error) { func acceptRequest(typ reqType, hydraURL string, fakeTLSTermination bool, challenge string, data interface{}) (string, error) {
if challenge == "" { if challenge == "" {
return "", ErrChallengeMissed return "", ErrChallengeMissed
} }
@ -101,6 +110,10 @@ func acceptRequest(typ reqType, hydraURL, challenge string, data interface{}) (s
if err != nil { if err != nil {
return "", err return "", err
} }
if fakeTLSTermination {
r.Header.Add("X-Forwarded-Proto", "https")
}
r.Header.Set("Content-Type", "application/json") r.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(r) resp, err := http.DefaultClient.Do(r)
if err != nil { if err != nil {

View File

@ -14,17 +14,18 @@ import (
// LoginReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process. // LoginReqDoer fetches information on the OAuth2 request and then accept or reject the requested authentication process.
type LoginReqDoer struct { type LoginReqDoer struct {
hydraURL string hydraURL string
fakeTLSTermination bool
rememberFor int rememberFor int
} }
// NewLoginReqDoer creates a LoginRequest. // NewLoginReqDoer creates a LoginRequest.
func NewLoginReqDoer(hydraURL string, rememberFor int) *LoginReqDoer { func NewLoginReqDoer(hydraURL string, fakeTLSTermination bool, rememberFor int) *LoginReqDoer {
return &LoginReqDoer{hydraURL: hydraURL, rememberFor: rememberFor} return &LoginReqDoer{hydraURL: hydraURL, fakeTLSTermination: fakeTLSTermination, rememberFor: rememberFor}
} }
// InitiateRequest fetches information on the OAuth2 request. // InitiateRequest fetches information on the OAuth2 request.
func (lrd *LoginReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) { func (lrd *LoginReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
ri, err := initiateRequest(login, lrd.hydraURL, challenge) ri, err := initiateRequest(login, lrd.hydraURL, lrd.fakeTLSTermination, challenge)
return ri, errors.Wrap(err, "failed to initiate login request") return ri, errors.Wrap(err, "failed to initiate login request")
} }
@ -39,6 +40,6 @@ func (lrd *LoginReqDoer) AcceptLoginRequest(challenge string, remember bool, sub
RememberFor: lrd.rememberFor, RememberFor: lrd.rememberFor,
Subject: subject, Subject: subject,
} }
redirectURI, err := acceptRequest(login, lrd.hydraURL, challenge, data) redirectURI, err := acceptRequest(login, lrd.hydraURL, lrd.fakeTLSTermination, challenge, data)
return redirectURI, errors.Wrap(err, "failed to accept login request") return redirectURI, errors.Wrap(err, "failed to accept login request")
} }

View File

@ -60,7 +60,7 @@ func TestInitiateLoginRequest(t *testing.T) {
h := &testInitiateLoginHandler{reqInfo: tc.reqInfo, status: tc.status} h := &testInitiateLoginHandler{reqInfo: tc.reqInfo, status: tc.status}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewLoginReqDoer(srv.URL, 0) ldr := hydra.NewLoginReqDoer(srv.URL, false, 0)
reqInfo, err := ldr.InitiateRequest(tc.challenge) reqInfo, err := ldr.InitiateRequest(tc.challenge)
@ -160,7 +160,7 @@ func TestAcceptLoginRequest(t *testing.T) {
h := &testAcceptLoginHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect} h := &testAcceptLoginHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewLoginReqDoer(srv.URL, tc.rememberFor) ldr := hydra.NewLoginReqDoer(srv.URL, false, tc.rememberFor)
redirect, err := ldr.AcceptLoginRequest(tc.challenge, tc.remember, tc.subject) redirect, err := ldr.AcceptLoginRequest(tc.challenge, tc.remember, tc.subject)

View File

@ -14,21 +14,22 @@ import (
// LogoutReqDoer fetches information on the OAuth2 request and then accepts or rejects the requested logout process. // LogoutReqDoer fetches information on the OAuth2 request and then accepts or rejects the requested logout process.
type LogoutReqDoer struct { type LogoutReqDoer struct {
hydraURL string hydraURL string
fakeTLSTermination bool
} }
// NewLogoutReqDoer creates a LogoutRequest. // NewLogoutReqDoer creates a LogoutRequest.
func NewLogoutReqDoer(hydraURL string) *LogoutReqDoer { func NewLogoutReqDoer(hydraURL string, fakeTLSTermination bool) *LogoutReqDoer {
return &LogoutReqDoer{hydraURL: hydraURL} return &LogoutReqDoer{hydraURL: hydraURL, fakeTLSTermination: fakeTLSTermination}
} }
// InitiateRequest fetches information on the OAuth2 request. // InitiateRequest fetches information on the OAuth2 request.
func (lrd *LogoutReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) { func (lrd *LogoutReqDoer) InitiateRequest(challenge string) (*ReqInfo, error) {
ri, err := initiateRequest(logout, lrd.hydraURL, challenge) ri, err := initiateRequest(logout, lrd.hydraURL, lrd.fakeTLSTermination, challenge)
return ri, errors.Wrap(err, "failed to initiate logout request") return ri, errors.Wrap(err, "failed to initiate logout request")
} }
// AcceptLogoutRequest accepts the requested logout process, and returns redirect URI. // AcceptLogoutRequest accepts the requested logout process, and returns redirect URI.
func (lrd *LogoutReqDoer) AcceptLogoutRequest(challenge string) (string, error) { func (lrd *LogoutReqDoer) AcceptLogoutRequest(challenge string) (string, error) {
redirectURI, err := acceptRequest(logout, lrd.hydraURL, challenge, nil) redirectURI, err := acceptRequest(logout, lrd.hydraURL, lrd.fakeTLSTermination, challenge, nil)
return redirectURI, errors.Wrap(err, "failed to accept logout request") return redirectURI, errors.Wrap(err, "failed to accept logout request")
} }

View File

@ -44,7 +44,7 @@ func TestInitiateLogoutRequest(t *testing.T) {
h := &testInitiateLogoutHandler{reqInfo: tc.reqInfo, status: tc.status} h := &testInitiateLogoutHandler{reqInfo: tc.reqInfo, status: tc.status}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewLogoutReqDoer(srv.URL) ldr := hydra.NewLogoutReqDoer(srv.URL, false)
reqInfo, err := ldr.InitiateRequest(tc.challenge) reqInfo, err := ldr.InitiateRequest(tc.challenge)
@ -126,7 +126,7 @@ func TestAcceptLogoutRequest(t *testing.T) {
h := &testAcceptLogoutHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect} h := &testAcceptLogoutHandler{challenge: tc.challenge, status: tc.status, redirect: tc.redirect}
srv := httptest.NewServer(h) srv := httptest.NewServer(h)
defer srv.Close() defer srv.Close()
ldr := hydra.NewLogoutReqDoer(srv.URL) ldr := hydra.NewLogoutReqDoer(srv.URL, false)
redirect, err := ldr.AcceptLogoutRequest(tc.challenge) redirect, err := ldr.AcceptLogoutRequest(tc.challenge)

View File

@ -30,6 +30,7 @@ type Config struct {
HydraURL string `envconfig:"hydra_url" required:"true" desc:"an admin URL of ORY Hydra Server"` HydraURL string `envconfig:"hydra_url" required:"true" desc:"an admin URL of ORY Hydra Server"`
SessionTTL time.Duration `envconfig:"session_ttl" default:"24h" desc:"a user session's TTL"` SessionTTL time.Duration `envconfig:"session_ttl" default:"24h" desc:"a user session's TTL"`
ClaimScopes map[string]string `envconfig:"claim_scopes" default:"name:profile,family_name:profile,given_name:profile,email:email,https%3A%2F%2Fgithub.com%2Fi-core%2Fwerther%2Fclaims%2Froles:roles" desc:"a mapping of OpenID Connect claims to scopes (all claims are URL encoded)"` ClaimScopes map[string]string `envconfig:"claim_scopes" default:"name:profile,family_name:profile,given_name:profile,email:email,https%3A%2F%2Fgithub.com%2Fi-core%2Fwerther%2Fclaims%2Froles:roles" desc:"a mapping of OpenID Connect claims to scopes (all claims are URL encoded)"`
FakeTLSTermination bool `envconfig:"fake_tls_termination" default:"false" desc:"Fake tls termination by adding \"X-Forwarded-Proto: https\" to http headers "`
} }
// UserManager is an interface that is used for authentication and providing user's claims. // UserManager is an interface that is used for authentication and providing user's claims.
@ -83,10 +84,10 @@ func NewHandler(cnf Config, um UserManager, tr TemplateRenderer) *Handler {
// AddRoutes registers all required routes for Login & Consent Provider. // AddRoutes registers all required routes for Login & Consent Provider.
func (h *Handler) AddRoutes(apply func(m, p string, h http.Handler, mws ...func(http.Handler) http.Handler)) { func (h *Handler) AddRoutes(apply func(m, p string, h http.Handler, mws ...func(http.Handler) http.Handler)) {
sessionTTL := int(h.SessionTTL.Seconds()) sessionTTL := int(h.SessionTTL.Seconds())
apply(http.MethodGet, "/login", newLoginStartHandler(hydra.NewLoginReqDoer(h.HydraURL, 0), h.tr)) apply(http.MethodGet, "/login", newLoginStartHandler(hydra.NewLoginReqDoer(h.HydraURL, h.FakeTLSTermination, 0), h.tr))
apply(http.MethodPost, "/login", newLoginEndHandler(hydra.NewLoginReqDoer(h.HydraURL, sessionTTL), h.um, h.tr)) apply(http.MethodPost, "/login", newLoginEndHandler(hydra.NewLoginReqDoer(h.HydraURL, h.FakeTLSTermination, sessionTTL), h.um, h.tr))
apply(http.MethodGet, "/consent", newConsentHandler(hydra.NewConsentReqDoer(h.HydraURL, sessionTTL), h.um, h.ClaimScopes)) apply(http.MethodGet, "/consent", newConsentHandler(hydra.NewConsentReqDoer(h.HydraURL, h.FakeTLSTermination, sessionTTL), h.um, h.ClaimScopes))
apply(http.MethodGet, "/logout", newLogoutHandler(hydra.NewLogoutReqDoer(h.HydraURL))) apply(http.MethodGet, "/logout", newLogoutHandler(hydra.NewLogoutReqDoer(h.HydraURL, h.FakeTLSTermination)))
} }
// oa2LoginReqAcceptor is an interface that is used for accepting an OAuth2 login request. // oa2LoginReqAcceptor is an interface that is used for accepting an OAuth2 login request.

View File

@ -52,8 +52,10 @@ type Config struct {
BindDN string `envconfig:"binddn" desc:"a LDAP bind DN"` BindDN string `envconfig:"binddn" desc:"a LDAP bind DN"`
BindPass string `envconfig:"bindpw" json:"-" desc:"a LDAP bind password"` BindPass string `envconfig:"bindpw" json:"-" desc:"a LDAP bind password"`
BaseDN string `envconfig:"basedn" required:"true" desc:"a LDAP base DN for searching users"` BaseDN string `envconfig:"basedn" required:"true" desc:"a LDAP base DN for searching users"`
UserSearchQuery string `envconfig:"user_search_query" desc:"the user search query" default:"(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))"`
AttrClaims map[string]string `envconfig:"attr_claims" default:"name:name,sn:family_name,givenName:given_name,mail:email" desc:"a mapping of LDAP attributes to OpenID connect claims"` AttrClaims map[string]string `envconfig:"attr_claims" default:"name:name,sn:family_name,givenName:given_name,mail:email" desc:"a mapping of LDAP attributes to OpenID connect claims"`
RoleBaseDN string `envconfig:"role_basedn" required:"true" desc:"a LDAP base DN for searching roles"` RoleBaseDN string `envconfig:"role_basedn" required:"true" desc:"a LDAP base DN for searching roles"`
RoleSearchQuery string `envconfig:"role_search_query" desc:"the role search query" default:"(|(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s)))"`
RoleAttr string `envconfig:"role_attr" default:"description" desc:"a LDAP group's attribute that contains a role's name"` RoleAttr string `envconfig:"role_attr" default:"description" desc:"a LDAP group's attribute that contains a role's name"`
RoleClaim string `envconfig:"role_claim" default:"https://github.com/i-core/werther/claims/roles" desc:"a name of an OpenID Connect claim that contains user roles"` RoleClaim string `envconfig:"role_claim" default:"https://github.com/i-core/werther/claims/roles" desc:"a name of an OpenID Connect claim that contains user roles"`
CacheSize int `envconfig:"cache_size" default:"512" desc:"a user info cache's size in KiB"` CacheSize int `envconfig:"cache_size" default:"512" desc:"a user info cache's size in KiB"`
@ -72,7 +74,13 @@ type Client struct {
func New(cnf Config) *Client { func New(cnf Config) *Client {
return &Client{ return &Client{
Config: cnf, Config: cnf,
connector: &ldapConnector{BaseDN: cnf.BaseDN, RoleBaseDN: cnf.RoleBaseDN, IsTLS: cnf.IsTLS}, connector: &ldapConnector{
BaseDN: cnf.BaseDN,
UserSearchQuery: cnf.UserSearchQuery,
RoleBaseDN: cnf.RoleBaseDN,
IsTLS: cnf.IsTLS,
RoleSearchQuery: cnf.RoleSearchQuery,
},
cache: freecache.NewCache(cnf.CacheSize * 1024), cache: freecache.NewCache(cnf.CacheSize * 1024),
} }
} }
@ -299,6 +307,8 @@ type ldapConnector struct {
BaseDN string BaseDN string
RoleBaseDN string RoleBaseDN string
IsTLS bool IsTLS bool
UserSearchQuery string
RoleSearchQuery string
} }
func (c *ldapConnector) Connect(ctx context.Context, addr string) (conn, error) { func (c *ldapConnector) Connect(ctx context.Context, addr string) (conn, error) {
@ -319,13 +329,21 @@ func (c *ldapConnector) Connect(ctx context.Context, addr string) (conn, error)
ldapcn := ldap.NewConn(tcpcn, c.IsTLS) ldapcn := ldap.NewConn(tcpcn, c.IsTLS)
ldapcn.Start() ldapcn.Start()
return &ldapConn{Conn: ldapcn, BaseDN: c.BaseDN, RoleBaseDN: c.RoleBaseDN}, nil return &ldapConn{
Conn: ldapcn,
BaseDN: c.BaseDN,
UserSearchQuery: c.UserSearchQuery,
RoleBaseDN: c.RoleBaseDN,
RoleSearchQuery: c.RoleSearchQuery,
}, nil
} }
type ldapConn struct { type ldapConn struct {
*ldap.Conn *ldap.Conn
BaseDN string BaseDN string
RoleBaseDN string RoleBaseDN string
UserSearchQuery string
RoleSearchQuery string
} }
func (c *ldapConn) Bind(bindDN, password string) error { func (c *ldapConn) Bind(bindDN, password string) error {
@ -337,17 +355,12 @@ func (c *ldapConn) Bind(bindDN, password string) error {
} }
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) { func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) {
query := fmt.Sprintf( query := fmt.Sprintf(c.UserSearchQuery, user)
"(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))"+
"(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))", user)
return c.searchEntries(c.BaseDN, query, attrs) return c.searchEntries(c.BaseDN, query, attrs)
} }
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) { func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) {
query := fmt.Sprintf("(|"+ query := fmt.Sprintf(c.RoleSearchQuery, user)
"(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))"+
"(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s))"+
")", user)
return c.searchEntries(c.RoleBaseDN, query, attrs) return c.searchEntries(c.RoleBaseDN, query, attrs)
} }

21
misc/script/build Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
set -e
DISTS=${DISTS:-linux/386 linux/amd64 windows/amd64 darwin/amd64}
for dist in $DISTS
do
os=`echo $dist | cut -d'/' -f1`
arch=`echo $dist | cut -d'/' -f2`
env GOOS=$os GOARCH=$arch go build -o bin/werther_${os}_${arch} -ldflags "-w -s -X main.version=$(git describe --tags)" ./cmd/werther
if [[ "$os" = "windows" ]]; then
zip -r bin/werther_${os}_${arch}.zip bin/werther_${os}_${arch}
else
tar cvzf bin/werther_${os}_${arch}.tar.gz bin/werther_${os}_${arch}
fi
done
(cd bin && sha256sum *.{tar.gz,zip} > werther_checksums.txt || exit 0)