diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2bed8e1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,83 @@
+language: go
+
+go:
+ - 1.12.x
+
+services:
+ - docker
+
+env:
+ global:
+ - CGO_ENABLED=0
+ - GO111MODULE=on
+ - GOPROXY=https://proxy.golang.org
+
+cache:
+ directories:
+ - "$GOPATH/pkg/mod"
+ - "$GOPATH/bin"
+
+install: "(cd $HOME && go get -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.16.0)"
+
+script:
+ - go test -v -coverprofile=coverage.txt ./...
+ - golangci-lint -v run
+ - |
+ set -e
+ for dist in linux/386 linux/amd64 windows/amd64 darwin/amd64
+ 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=$TRAVIS_TAG" ./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)
+ - |
+ set -e
+ docker build --build-arg GOPROXY --build-arg VERSION=$TRAVIS_TAG -t "icoreru/werther:$TRAVIS_COMMIT" .
+ if [ -n "$TRAVIS_TAG" ]; then
+ docker tag "icoreru/werther:$TRAVIS_COMMIT" "icoreru/werther:$TRAVIS_TAG"
+ docker tag "icoreru/werther:$TRAVIS_COMMIT" "icoreru/werther:latest"
+ fi
+
+after_success:
+ - bash <(curl -s https://codecov.io/bash)
+
+before_deploy:
+ - |
+ if [ -n "$TRAVIS_TAG" ]; then
+ docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"
+ fi
+
+deploy:
+ - provider: releases
+ api_key:
+ secure: f3KYUKsrtYRKcttPfWmHWGCFT2ugas1fgbBACGCTFp/ir6AAfHt16FYHgyfXc8+V9IajNkwqL6TrDjQFfLSI0qx38s+wLK6FIicjvMzVWXe/lCHByr4kAZuN2BOynrKiE8rkEcXHGjX2adrrvETNwUCp+oIxQtdIAVvE1Fjkb+MdnCs7Ed9beggqNOJksGVJ44X17ezarCc8/L4ULIXY/OBLnUnwH6UwMrSIPuwvMJAZlhyJWOv4ro8Z3D2f5vfD91MNit8rCkXkYPKnw1/rIpBbaoARLQ95bN97NsLkfeNlSgoAXhy00i+Jz78PgD3TvTPecdlTPwBNNnHaXBtQZjB+qHpr4lW/NP2+IJ6Aku8JY2X+Srd/BYD8hh4Nqzp3UiymsQS61++jfZmi3xUu5nhFkd+MavVW8Xy0/8vnREqHuwCQ8+oo1GHqDnKgdeRMm29AwTTx/FyUSPlzWzQIC1PVFtS3/YYqAn7sooS6l5MuSENk05IYOM1ApXOGb6tNW8wDGTD8QP8KvJjfARg8365wwhEAP6gdrW6VotSjY5XZM37ge0uKfKBvw8BVNfbn/R4/12KIuqPsEmbVfFJx18DQzz3b+9UfPZQwuxZvgNnngplUbzP2q/cKYNSMHKzZ53EVPPr5wtdDWm5pnbLtWbrN5d+y2FoS+YBrCrL09C8=
+ file:
+ - bin/werther_linux_386.tar.gz
+ - bin/werther_linux_amd64.tar.gz
+ - bin/werther_windows_amd64.zip
+ - bin/werther_darwin_amd64.tar.gz
+ - bin/werther_checksums.txt
+ skip_cleanup: true
+ on:
+ tags: true
+ condition: $TRAVIS_OS_NAME = linux
+
+ - provider: script
+ skip_cleanup: true
+ script: docker push "icoreru/werther:$TRAVIS_TAG"
+ on:
+ tags: true
+ condition: $TRAVIS_OS_NAME = linux
+
+ - provider: script
+ skip_cleanup: true
+ script: docker push "icoreru/werther:latest"
+ on:
+ tags: true
+ condition: $TRAVIS_OS_NAME = linux
diff --git a/README.md b/README.md
index b6dc822..7e7e517 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Werther [1](#myfootnote1)
-[![GoDoc][doc-img]][doc] [![Build Status][build-img]][build] [![codecov][codecov-img]][codecov]
+[![GoDoc][doc-img]][doc] [![Build Status][build-img]][build] [![codecov][codecov-img]][codecov] [![Go Report Card][goreport-img]][goreport]
Werther is an Identity Provider for [ORY Hydra][hydra] over [LDAP][ldap].
It implements [Login And Consent Flow][hydra-login-consent] and provides basic UI.
@@ -30,10 +30,10 @@ ORY Hydra v1.0.0-rc.12 or higher.
- [Installing](#installing)
-- [Usage](#usage)
- [Configuration](#configuration)
- [User roles](#user-roles)
- [UI customization](#ui-customization)
+- [Example](#example)
- [Resources](#resources)
- [Footnotes](#footnotes)
- [Contributing](#contributing)
@@ -46,7 +46,7 @@ ORY Hydra v1.0.0-rc.12 or higher.
### From Docker
```bash
-docker pull icoreru/werter
+docker pull icoreru/werther
```
### From sources
@@ -55,44 +55,6 @@ docker pull icoreru/werter
go install ./...
```
-## Usage
-
-1. Create a network:
- ```
- docker network create hydra-net
- ```
-
-2. Run ORY Hydra:
- ```
- docker run --network hydra-net -d --restart always --name hydra \
- -p 4444:4444 \
- -p 4445:4445 \
- -e URLS_SELF_ISSUER=http://localhost:4444 \
- -e URLS_SELF_PUBLIC=http://localhost:4444 \
- -e URLS_LOGIN=http://localhost:8080/auth/login \
- -e URLS_CONSENT=http://localhost:8080/auth/consent \
- -e URLS_LOGOUT=http://localhost:8080/auth/logout \
- -e WEBFINGER_OIDC_DISCOVERY_SUPPORTED_SCOPES=profile,email,phone \
- -e WEBFINGER_OIDC_DISCOVERY_SUPPORTED_CLAIMS=name,family_name,given_name,nickname,email,phone_number \
- -e DSN=memory \
- oryd/hydra:v1.0.0-rc.12 serve all
- ```
-
- Look for details in [ORY Hydra Configuration][hydra-doc-config] and [ORY Hydra Documentation][hydra-doc].
-
-3. Run Werther:
- ```
- docker run --network hydra-net -d --restart always --name werther \
- -p 8080:8080 \
- -e WERTHER_IDENTP_HYDRA_URL=http://hydra:4445 \
- -e WERTHER_LDAP_ENDPOINTS=icdc0.example.local:389,icdc1.example.local:389 \
- -e WERTHER_LDAP_BINDDN= \
- -e WERTHER_LDAP_BINDPW= \
- -e WERTHER_LDAP_BASEDN="DC=example,DC=local" \
- -e WERTHER_LDAP_ROLE_BASEDN="OU=AppRoles,OU=Domain Groups,DC=example,DC=local" \
- icoreru/werther
- ```
-
## Configuration
The application is configured via environment variables.
@@ -113,16 +75,16 @@ For example, create an OU that repserents an application, and then in the create
create groups that represent application's roles:
```
-DC=local
-|-- OU=Domain Groups
- |-- OU=AppRoles
- |-- OU=App1
- |-- CN=app1_role1 (objectClass="group", description="role1")
- |-- CN=app1_role2 (objectClass="group", description="role2")
+dc=com
+|-- dc=example
+ |-- ou=AppRoles
+ |-- ou=App1
+ |-- cn=app1_role1 (objectClass="group", description="role1")
+ |-- cn=app1_role2 (objectClass="group", description="role2")
```
Run Werther with the environment variable `WERTHER_LDAP_ROLE_DN`
-that equals to `OU=AppRoles,OU=Domain Groups,DC=local`.
+that equals to `ou=AppRoles,dc=example,dc=com`.
In the above example Werther returns user's roles as a value
of the user role's claim `https://github.com/i-core/werther/claims/roles`.
@@ -142,23 +104,23 @@ For more details about claims naming see [OpenID Connect Core 1.0][oidc-spec-add
For example, when we want to configure multiple applications or several environments for the same application.
```
-DC=local
-|-- OU=Domain Groups
- |-- OU=AppRoles
- |-- OU=Test
- |-- OU=App1
- |-- CN=test_app1_role1 (objectClass="group", description="role1")
- |-- CN=test_app1_role2 (objectClass="group", description="role2")
- |-- OU=App2
- |-- CN=test_app2_role1 (objectClass="group",description-"role1")
- |-- CN=test_app2_role2 (objectClass="group",description-"role2")
- |-- OU=Dev
- |-- OU=App1
- |-- CN=dev_app1_role1 (objectClass="group", description="role1")
- |-- CN=dev_app1_role3 (objectClass="group", description="role3")
- |-- OU=App2
- |-- CN=dev_app2_role1 (objectClass="group",description-"role1")
- |-- CN=dev_app2_role4 (objectClass="group",description-"role4")
+dc=com
+|-- dc=example
+ |-- ou=AppRoles
+ |-- ou=Test
+ |-- ou=App1
+ |-- cn=test_app1_role1 (objectClass="group", description="role1")
+ |-- cn=test_app1_role2 (objectClass="group", description="role2")
+ |-- ou=App2
+ |-- cn=test_app2_role1 (objectClass="group",description-"role1")
+ |-- cn=test_app2_role2 (objectClass="group",description-"role2")
+ |-- ou=Dev
+ |-- ou=App1
+ |-- cn=dev_app1_role1 (objectClass="group", description="role1")
+ |-- cn=dev_app1_role3 (objectClass="group", description="role3")
+ |-- ou=App2
+ |-- cn=dev_app2_role1 (objectClass="group",description-"role1")
+ |-- cn=dev_app2_role4 (objectClass="group",description-"role4")
```
Active Directory requires unique CNs in a domain. But in Active Directory
@@ -168,7 +130,7 @@ A name of a LDAP attribute is specified using the environment variable `WERTHER_
and has the default value `description`.
In the above example, Werther returns a response that contains the next roles:
-* when the environment variable `WERTHER_LDAP_ROLE_DN` equals to `OU=Test,OU=AppRoles,OU=Domain Groups,DC=local`:
+* when the environment variable `WERTHER_LDAP_ROLE_DN` equals to `ou=Test,ou=AppRoles,dc=example,dc=com`:
```json
{
"https://github.com/i-core/werther/claims/roles": {
@@ -177,7 +139,7 @@ In the above example, Werther returns a response that contains the next roles:
}
}
```
-* when the environment variable `WERTHER_LDAP_ROLE_DN` equals to `OU=Dev,OU=AppRoles,OU=Domain Groups,DC=local`:
+* when the environment variable `WERTHER_LDAP_ROLE_DN` equals to `ou=Dev,ou=AppRoles,dc=example,dc=com`:
```json
{
"https://github.com/i-core/werther/claims/roles": {
@@ -191,21 +153,7 @@ In the above example, Werther returns a response that contains the next roles:
Werther uses the Go templates to render UI pages.
To customize the UI you should create a directory that contains UI pages' templates.
-After that you should set the directory path to the environment variable `WERTHER_WEB_DIR`:
-
-```bash
-docker run --network hydra-net -d --restart always --name werther \
- -p 8080:8080 \
- -v /opt/werther/web:/path/to/custom-login-page/dir \
- -e WERTHER_IDENTP_HYDRA_URL=http://hydra:4445 \
- -e WERTHER_LDAP_ENDPOINTS=icdc0.example.local:389,icdc1.example.local:389 \
- -e WERTHER_LDAP_BINDDN= \
- -e WERTHER_LDAP_BINDPW= \
- -e WERTHER_LDAP_BASEDN="DC=example,DC=local" \
- -e WERTHER_LDAP_ROLE_BASEDN="OU=AppRoles,OU=Domain Groups,DC=example,DC=local" \
- -e WERTHER_WEB_DIR=/opt/werther/web
- icoreru/werther
-```
+After that you should set the directory path to the environment variable `WERTHER_WEB_DIR`.
### Custom login page
@@ -222,11 +170,137 @@ they must be placed in a subdirectory called `static`.
For a full example of a login page's template see [source code](internal/web/templates).
+## Example
+
+1. Create file `ldap.ldif`:
+ ```
+ dn: uid=kolya_gerasyimov,ou=Users,dc=example,dc=com
+ objectClass: inetOrgPerson
+ cn: Kolya Gerasyimov
+ sn: Gerasyimov
+ uid: kolya_gerasyimov
+ userPassword: 123
+ mail: kolya_gerasyimov@example.com
+ ou: Users
+
+ dn: ou=AppRoles,dc=example,dc=com
+ objectClass: organizationalunit
+ ou: AppRoles
+ description: AppRoles
+
+ dn: ou=App1,ou=AppRoles,dc=example,dc=com
+ objectClass: organizationalunit
+ ou: App1
+ description: App1
+
+ dn: cn=traveler,ou=App1,ou=AppRoles,dc=example,dc=com
+ objectClass: groupofnames
+ cn: traveler
+ description: traveler
+ member: uid=kolya_gerasyimov,ou=Users,dc=example,dc=com
+ ```
+
+2. Create file `docker-compose.yml`:
+ ```yaml
+ version: "3"
+ services:
+ hydra-client:
+ image: oryd/hydra:v1.0.0-rc.12
+ environment:
+ HYDRA_ADMIN_URL: http://hydra:4445
+ command:
+ - clients
+ - create
+ - --skip-tls-verify
+ - --id
+ - test-client
+ - --secret
+ - test-secret
+ - --response-types
+ - id_token,token,"id_token token"
+ - --grant-types
+ - implicit
+ - --scope
+ - openid,profile,email
+ - --callbacks
+ - http://localhost:3000
+ - --post-logout-callbacks
+ - http://localhost:3000/post-logout-callback
+ networks:
+ - hydra-net
+ deploy:
+ restart_policy:
+ condition: none
+ depends_on:
+ - hydra
+ hydra:
+ image: oryd/hydra:v1.0.0-rc.12
+ environment:
+ URLS_SELF_ISSUER: http://localhost:4444
+ URLS_SELF_PUBLIC: http://localhost:4444
+ URLS_LOGIN: http://localhost:8080/auth/login
+ URLS_CONSENT: http://localhost:8080/auth/consent
+ URLS_LOGOUT: http://localhost:8080/auth/logout
+ WEBFINGER_OIDC_DISCOVERY_SUPPORTED_SCOPES: profile,email,phone
+ WEBFINGER_OIDC_DISCOVERY_SUPPORTED_CLAIMS: name,family_name,given_name,nickname,email,phone_number
+ DSN: memory
+ command: serve all --dangerous-force-http
+ networks:
+ - hydra-net
+ ports:
+ - "4444:4444"
+ - "4445:4445"
+ deploy:
+ restart_policy:
+ condition: on-failure
+ depends_on:
+ - werther
+ werther:
+ image: icoreru/werther:v1.0.0
+ environment:
+ WERTHER_IDENTP_HYDRA_URL: http://hydra:4445
+ WERTHER_LDAP_ENDPOINTS: ldap:389
+ WERTHER_LDAP_BINDDN: cn=admin,dc=example,dc=com
+ WERTHER_LDAP_BINDPW: password
+ WERTHER_LDAP_BASEDN: "dc=example,dc=com"
+ WERTHER_LDAP_ROLE_BASEDN: "ou=AppRoles,dc=example,dc=com"
+ networks:
+ - hydra-net
+ ports:
+ - "8080:8080"
+ deploy:
+ restart_policy:
+ condition: on-failure
+ depends_on:
+ - ldap
+ ldap:
+ image: pgarrett/ldap-alpine
+ volumes:
+ - "./ldap.ldif:/ldif/ldap.ldif"
+ networks:
+ - hydra-net
+ ports:
+ - "389:389"
+ deploy:
+ restart_policy:
+ condition: on-failure
+ networks:
+ hydra-net:
+ ```
+
+3. Run the command:
+ ```bash
+ docker stack deploy docker-compose.yml auth
+ ```
+
+4. Open the browser with http://localhost:4444/oauth2/auth?client_id=test-client&response_type=token&scope=openid%20profile%20email&state=12345678.
+
## Resources
- [Introduction to ORY Hydra, OAuth 2.0, and OpenID Connect][hydra-doc];
- [ORY Hydra: Integrating with (existing) User Management][hydra-login-consent];
-- [Official User Login & Consent Example](https://github.com/ory/hydra-login-consent-node);
+- [ORY Hydra: Configuration][hydra-doc-config];
+- [ORY Hydra: Official User Login & Consent Example][hydra-login-consent-example];
- [OpenID Connect Core 1.0][oidc-spec-core];
- [OpenID Connect Session Management 1.0][oidc-spec-session];
- [OpenID Connect Front-Channel Logout 1.0][oidc-spec-front-channel-logout];
@@ -254,6 +328,9 @@ The code in this project is licensed under [MIT license][license].
[codecov-img]: https://codecov.io/gh/i-core/werther/branch/master/graph/badge.svg
[codecov]: https://codecov.io/gh/i-core/werther
+[goreport-img]: https://goreportcard.com/badge/github.com/i-core/werther
+[goreport]: https://goreportcard.com/report/github.com/i-core/werther
+
[contrib]: https://github.com/i-core/.github/blob/master/CONTRIBUTING.md
[license]: LICENSE
@@ -263,6 +340,7 @@ The code in this project is licensed under [MIT license][license].
[hydra]: https://www.ory.sh/
[hydra-doc]: https://www.ory.sh/docs/hydra/
[hydra-login-consent]: https://www.ory.sh/docs/hydra/oauth2
+[hydra-login-consent-example]: https://github.com/ory/hydra-login-consent-node
[hydra-doc-config]: https://www.ory.sh/docs/hydra/configuration
[oidc-spec-core]: https://openid.net/specs/openid-connect-core-1_0.html
diff --git a/internal/ldapclient/ldapclient_test.go b/internal/ldapclient/ldapclient_test.go
index 37d3264..668320d 100644
--- a/internal/ldapclient/ldapclient_test.go
+++ b/internal/ldapclient/ldapclient_test.go
@@ -30,8 +30,8 @@ var (
"b": "valB",
"c": "valC",
"roles": []map[string]interface{}{
- {"dn": "CN=role1,OU=app1,OU=test,DC=local", "test-roles-attr": "r1"},
- {"dn": "CN=role2,OU=app1,OU=test,DC=local", "test-roles-attr": "r2"},
+ {"dn": "cn=role1,ou=app1,ou=test,dc=local", "test-roles-attr": "r1"},
+ {"dn": "cn=role2,ou=app1,ou=test,dc=local", "test-roles-attr": "r2"},
},
},
{
@@ -41,10 +41,10 @@ var (
"b": "valB",
"c": "valC",
"roles": []map[string]interface{}{
- {"dn": "CN=role1,OU=app1,OU=test,DC=local", "test-roles-attr": "r1"},
- {"dn": "CN=role2,OU=app1,OU=test,DC=local", "test-roles-attr": "r2"},
- {"dn": "CN=role3,OU=app2,OU=test,DC=local", "test-roles-attr": "r3"},
- {"dn": "CN=role4,OU=app2,OU=test,DC=local", "test-roles-attr": "r4"},
+ {"dn": "cn=role1,ou=app1,ou=test,dc=local", "test-roles-attr": "r1"},
+ {"dn": "cn=role2,ou=app1,ou=test,dc=local", "test-roles-attr": "r2"},
+ {"dn": "cn=role3,ou=app2,ou=test,dc=local", "test-roles-attr": "r3"},
+ {"dn": "cn=role4,ou=app2,ou=test,dc=local", "test-roles-attr": "r4"},
},
},
{
@@ -54,7 +54,7 @@ var (
"b": "valB",
"c": "valC",
"roles": []map[string]interface{}{
- {"dn": "CN=role1,OU=app1,OU=test,DC=local", "test-roles-attr": "r1"},
+ {"dn": "cn=role1,ou=app1,ou=test,dc=local", "test-roles-attr": "r1"},
{"test-roles-attr": "r2"},
},
},
@@ -65,8 +65,8 @@ var (
"b": "valB",
"c": "valC",
"roles": []map[string]interface{}{
- {"dn": "CN=role1,OU=app1,OU=test,DC=local", "test-roles-attr": "r1"},
- {"dn": "CN=role2,OU=app1,OU=test,DC=local"},
+ {"dn": "cn=role1,ou=app1,ou=test,dc=local", "test-roles-attr": "r1"},
+ {"dn": "cn=role2,ou=app1,ou=test,dc=local"},
},
},
{
@@ -76,7 +76,7 @@ var (
"b": "valB",
"c": "valC",
"roles": []map[string]interface{}{
- {"dn": "CN=role1,OU=test,DC=local", "test-roles-attr": "r1"},
+ {"dn": "cn=role1,ou=test,dc=local", "test-roles-attr": "r1"},
},
},
{
@@ -375,7 +375,7 @@ func TestFindOIDCClaims(t *testing.T) {
BindDN: tc.bindDN,
BindPass: tc.bindPass,
AttrClaims: tc.attrClaims,
- RoleBaseDN: "OU=test,DC=local",
+ RoleBaseDN: "ou=test,dc=local",
RoleClaim: "test-roles-claim",
RoleAttr: "test-roles-attr",
})
@@ -409,7 +409,7 @@ func TestClaimsCache(t *testing.T) {
client := New(Config{
Endpoints: connector.Endpoints(),
AttrClaims: map[string]string{"dn": "name", "a": "claimA", "d": "claimD"},
- RoleBaseDN: "OU=test,DC=local",
+ RoleBaseDN: "ou=test,dc=local",
RoleClaim: "test-roles-claim",
RoleAttr: "test-roles-attr",
})