Giter Club home page Giter Club logo

werther's Introduction

Werther 1

GoDoc Build Status codecov Go Report Card

Werther is an Identity Provider for ORY Hydra over LDAP. It implements Login And Consent Flow and provides basic UI.

screenshot

Features

  • Support Active Directory;
  • Mapping LDAP attributes to OpenID Connect claims;
  • Mapping LDAP groups to user roles;
  • OAuth 2.0 scopes;
  • Caching users roles;
  • UI customization.

Limitations

  • Werther grants all requested permissions to a client without displaying the consent page;
  • Werther confirms a logout request without displaying the logout confirmation page.

Requirements

ORY Hydra v1.0.0-rc.12 or higher.

Table of Contents

Installing

From Docker

docker pull icoreru/werther

From sources

go install ./...

Configuration

The application is configured via environment variables. Names of the environment variables starts with prefix WERTHER_. See a list of the environment variables using the command:

werther -h

User roles

In LDAP user's roles are groups in which a user is a member.

The environment variable WERTHER_LDAP_ROLE_BASEDN is a DN for searching roles.

For example, create an OU that repserents an application, and then in the created OU create groups that represent application's roles:

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_BASEDN 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.

{
    "https://github.com/i-core/werther/claims/roles": {
        "App1": ["role1", "role2"],
    }
}

To customize the roles claim's name you should set a value of the environment variable WERTHER_LDAP_ROLE_CLAIM. Also you should map the custom name of the roles' claim to a roles's scope using the environment variable WERTHER_IDENTP_CLAIM_SCOPES (the name must be URL encoded):

env WERTHER_LDAP_ROLE_CLAIM=https://my-company.com/claims/roles                                                                                     \
    WERTHER_IDENTP_CLAIM_SCOPES=name:profile,family_name:profile,given_name:profile,email:email,https%3A%2F%2Fmy-company.com%2Fclaims%2Froles:roles \
    werther

For more details about claims naming see OpenID Connect Core 1.0.

NB There are cases when we need to create several roles with the same name in LDAP. For example, when we want to configure multiple applications or several environments for the same application.

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 creating groups with the same CN in different OUs is difficult. Because of it, Werther uses a LDAP attribute as a role's name instead of CN. A name of a LDAP attribute is specified using the environment variable WERTHER_LDAP_ROLE_ATTR, 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_BASEDN equals to ou=Test,ou=AppRoles,dc=example,dc=com:
    {
        "https://github.com/i-core/werther/claims/roles": {
            "App1": ["role1", "role2"],
            "App2": ["role1", "role2"]
        }
    }
  • when the environment variable WERTHER_LDAP_ROLE_BASEDN equals to ou=Dev,ou=AppRoles,dc=example,dc=com:
    {
        "https://github.com/i-core/werther/claims/roles": {
            "App1": ["role1", "role3"],
            "App2": ["role1", "role4"]
        }
    }

If your applications expect the roles claim to be an array of strings, for example Concourse or Argo CD, you can add groups to claims using with the environment variable WERTHER_LDAP_FLAT_ROLE_CLAIMS. When it is true Werther add corresponding claims for all the apps as an array of roles.

Example 1:

WERTHER_LDAP_FLAT_ROLE_CLAIMS=false

{
    "https://github.com/i-core/werther/claims/roles": {
        "App1": ["role1", "role2"],
        "App2": ["role3", "role4"]
    }
}

Example 2:

WERTHER_LDAP_FLAT_ROLE_CLAIMS=true

{
    "https://github.com/i-core/werther/claims/roles": {
        "App1": ["role1", "role2"],
        "App2": ["role3", "role4"]
    },
    "https://github.com/i-core/werther/claims/roles/App1": ["role1", "role2"],
    "https://github.com/i-core/werther/claims/roles/App2": ["role3", "role4"]
}

UI customization

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.

Custom login page

A login page's template must be a Go template. The template has access to data conforming the next JSON-schema:

type: object
properties:
  - WebBasePath:
      description: The base path of the login page
      type: string
  - LangPrefs:
      description: The user language preferences (the parsed value of the header Accept-Language)
      type: array
      items:
        type: object
        properties:
          - Lang:
              description: The language canonical name.
              type: string
          - Weight:
              description: The language weight.
              type: number
        required:
          - Lang
          - Weight
  - Data:
      type: object
      properties:
        - CSRFToken:
            description: A CSRF token.
            type: string
        - Challenge:
            description: A login challenge ID.
            type: string
        - LoginURL:
            description: An endpoint that finishes the login process.
            type: string
        - IsInvalidCredentials:
            description: Specifies that a user types an invalid username or password.
            type: boolean
        - IsInternalError:
            description: Specifies that an internal server error happens when finishing the login process.
            type: boolean
      required:
        - CSRFToken
        - Challenge
        - LoginURL
        - IsInvalidCredentials
        - IsInternalError
required:
  - WebBasePath
  - LangPrefs
  - Data

When a login page's template contains static resources (like styles, scripts, and images) they must be placed in a subdirectory called static.

For a full example of a login page's template see source code.

Custom login page (old format)

The old template format is also supported but it will be removed in the future major release.

A login page's template should contains blocks title, style, script, content. Each block has access to data conforming the next JSON-schema:

type: object
properties:
  - CSRFToken:
    description: A CSRF token.
    type: string
  - Challenge:
    description: A login challenge ID.
    type: string
  - LoginURL:
    description: An endpoint that finishes the login process.
    type: string
  - IsInvalidCredentials:
    description: Specifies that a user types an invalid username or password.
    type: boolean
  - IsInternalError:
    description: Specifies that an internal server error happens when finishing the login process.
    type: boolean
required:
  - CSRFToken
  - Challenge
  - LoginURL
  - IsInvalidCredentials
  - IsInternalError

When a login page's template contains static resources (like styles, scripts, and images) they must be placed in a subdirectory called static.

For a full example of a login page's template see source code.

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: [email protected]
    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:

    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,roles
                - --callbacks
                - http://localhost:3000
                - --post-logout-callbacks
                - http://localhost:3000/post-logout-callback
            networks:
                - hydra-net
            deploy:
                restart_policy:
                    condition: none
            depends_on:
                - hydra
            healthcheck:
                test: ["CMD", "curl", "-f", "http://hydra:4445"]
                interval: 10s
                timeout: 10s
                retries: 10
        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,roles
                WEBFINGER_OIDC_DISCOVERY_SUPPORTED_CLAIMS: name,family_name,given_name,nickname,email,phone_number,https://github.com/i-core/werther/claims/roles
                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.1.1
            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:

    docker stack deploy -c 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%20roles&state=12345678.

Resources

Footnotes

  1. Werther is named after robot Werther from Guest from the Future.

Contributing

Thanks for your interest in contributing to this project. Get started with our Contributing Guide.

License

The code in this project is licensed under MIT license.

werther's People

Contributors

asherslab avatar cshazi-plensys avatar dre2004 avatar kfreiman avatar konstlepa avatar nikolaas avatar seanhoughton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

werther's Issues

translation and internationalization

Hi,
thanks for providing this good hydra companion :)
I'd like to install it for some french users but it will be easier for them if we can translate UI.
I can work on it, but I don't know anything about i18n stuff with golang, do you have any opinion? A library of choice?

Support clients needing roles claim containing a single list

Is your feature request related to a problem? Please describe.
The readme describes that the role claim generated by werther has the following format:

{
    "https://github.com/i-core/werther/claims/roles": {
        "App1": ["role1", "role2"],
    }
}

However, I believe that there exist several apps that need the following format instead:

{
    "https://github.com/i-core/werther/claims/roles": ["role1", "role2"]
}

Describe the solution you'd like
I am not sure whether the following is the best solution, but I propose the following. For each app (e.g. App1), also add a claim

{
    "https://github.com/i-core/werther/claims/roles/App1": ["role1", "role2"]
}

Describe alternatives you've considered

  • Or just flatten the roles dictionary.
  • Or use a different scope for each app.

Additional context

I'm trying to set up Kantega SSO for Confluence, using the OpenID Connect protocol.

After invalid credentials werther incorrectly redirects.

Describe the bug

I have WERTHER_WEB_BASE_PATH set to /werther/ which works correctly to show the UI.

There are two cases that can happen:

  • Successful login: Everything works as expected
  • Invalid Credentials: everything seems to work correctly, the user is redirected to the correct page, but this time, if I send the correct credentials the base path is no longer respected, and instead of POST-ing to /werther/auth/login, it POSTs to /auth/login

To Reproduce
Steps to reproduce the behavior:

  1. Set WERTHER_WEB_BASE_PATH
  2. Navigate to the new URL
  3. Put in invalid credentials
  4. Put in valid credentials <- this will redirect wrongly

Expected behavior

The base path is respected even on retries

Desktop (please complete the following information):

  • OS: macOS
  • Browser: Firefox
  • Version: 121.0b6

Additional context
Add any other context about the problem here.

Enable additional logging

Hello,

I'm having some problems getting this working with an outside ldap server (openldap), I get an incorrect username or password error when logging in but I'd like to see some debug information so I can determine what the root cause is.

Right now I'm thinking it could be one of three things.

  1. The ldap property (cn=user or uid=user) isn't what is being used to authenticate

  2. The encryption algorithm that I've used to encrypt the password doesn't match what the ldap client is using.

  3. Another configuration problem that I'm not yet seeing.

Werther over HTTPS

Describe the bug
I'm struggling to build this container and enable HTTPS on werther. I'm happy that I have it working with HTTP, and now just needs to add the certificates and enable HTTPS.

I created a certificate and used it with hydra and switched that to https, and thought I also need to do this with werther. But I can't figure out how. I even put Nginx in front of it to handle the https and proxy to werther, but it still complains.

It's probably a lack of understanding on my part, but I'd be grateful for any pointers.

To Reproduce
Steps to reproduce the behavior:
My docker-compose.yml

version: "3"

services:
  hydra-client:
    image: oryd/hydra:v1.0.0-rc.12
    environment:
      HYDRA_ADMIN_URL: https://hydra:4445
    command:
      - clients
      - create
      - --skip-tls-verify
      - --id
      - ${CLIENT_ID}
      - --secret
      - ${CLIENT_SECRET}
      - --response-types
      - id_token,token,"id_token token"
      - --grant-types
      - implicit
      - --scope
      - openid,profile,email
      - --callbacks
      - ${CALLBACK}
      - --post-logout-callbacks
      - ${CALLBACK_LOGOUT}
    deploy:
      restart_policy:
        condition: none
    depends_on:
      - hydra
    healthcheck:
      test: ["CMD", "curl", "-f", "https://hydra:4445"]
      interval: 10s
      timeout: 10s
      retries: 10

  hydra:
    image: oryd/hydra:v1.0.0-rc.12
    environment:
      URLS_SELF_ISSUER: https://localhost:4444
      URLS_SELF_PUBLIC: https://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
      HTTPS_TLS_CERT_PATH: ${PWD}/certs/ldap.crt
      HTTPS_TLS_KEY_PATH: ${PWD}/certs/ldap.key
    # command: serve all --dangerous-force-http
    command: serve all
    ports:
      - "4444:4444"
      - "4445:4445"
    deploy:
      restart_policy:
        condition: on-failure
    depends_on:
      - werther

  werther:
    image: icoreru/werther:v1.0.0
    environment:
      WERTHER_DEV_MODE: 1
      WERTHER_IDENTP_HYDRA_URL: http://hydra:4445
      WERTHER_ENDPOINTS: ${LDAP_URI}
      WERTHER_LDAP_ENDPOINTS: ${LDAP_URI}
      WERTHER_LDAP_BINDDN: ${LDAP_BINDDN}
      WERTHER_LDAP_BINDPW: ${LDAP_BINDPW}
      WERTHER_LDAP_BASEDN: ${LDAP_BASEDN}
      WERTHER_LDAP_ROLE_BASEDN: "ou=Roles,${LDAP_BASEDN}"
      WERTHER_LDAP_IS_TLS: ${LDAP_TLS}
      WERTHER_LDAP_ATTR_CLAIMS: "cn:name,sn:family_name,givenName:given_name,mail:mail"
    ports:
      - "8080:8080"
    deploy:
      restart_policy:
        condition: on-failure

Test URL

https://localhost:4444/oauth2/auth?client_id=test-client&response_type=token&scope=openid%20profile%20email&state=12345678

Docker Log Output

werther_1       | 2020-04-01T13:17:46.884Z      INFO    [email protected]/rlog.go:43  New request     {"requestID": "5583550f-ce4b-4d91-b925-25da7a46106d", "method": "GET", "url": "/auth/login?login_challenge=ff6b970735544db9a68f2fb4490054ec"}
werther_1       | 2020-04-01T13:17:46.885Z      INFO    identp/identp.go:128    Failed to initiate an OAuth2 login request      {"requestID": "5583550f-ce4b-4d91-b925-25da7a46106d", "error": "failed to initiate login request: invalid character 'C' looking for beginning of value", "errorVerbose": "invalid character 'C' looking for beginning of value\nfailed to initiate login request\ngithub.com/i-core/werther/internal/hydra.(*LoginReqDoer).InitiateRequest\n\t/opt/build/internal/hydra/login.go:28\ngithub.com/i-core/werther/internal/identp.newLoginStartHandler.func1\n\t/opt/build/internal/identp/identp.go:116\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:1995\ngithub.com/i-core/routegroup.(*Router).handler.func1\n\t/go/pkg/mod/github.com/i-core/[email protected]/routegroup.go:65\ngithub.com/julienschmidt/httprouter.(*Router).ServeHTTP\n\t/go/pkg/mod/github.com/julienschmidt/[email protected]/router.go:334\ngithub.com/i-core/rlog.NewMiddleware.func1.1\n\t/go/pkg/mod/github.com/i-core/[email protected]/rlog.go:48\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:1995\ngithub.com/justinas/nosurf.(*CSRFHandler).handleSuccess\n\t/go/pkg/mod/github.com/justinas/[email protected]/handler.go:179\ngithub.com/justinas/nosurf.(*CSRFHandler).ServeHTTP\n\t/go/pkg/mod/github.com/justinas/[email protected]/handler.go:136\nnet/http.serverHandler.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2774\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:1878\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1337", "challenge": "ff6b970735544db9a68f2fb4490054ec"}
werther_1       | 2020-04-01T13:17:46.885Z      DEBUG   [email protected]/rlog.go:49  The request is handled  {"requestID": "5583550f-ce4b-4d91-b925-25da7a46106d", "httpStatus": 500, "duration": "1.012755ms"}
werther_1       | 2020-04-01T13:17:46.948Z      INFO    [email protected]/rlog.go:43  New request     {"requestID": "8a455c6d-ef60-4d2e-ac01-d95d5227fa93", "method": "GET", "url": "/favicon.ico"}
werther_1       | 2020-04-01T13:17:46.948Z      DEBUG   [email protected]/rlog.go:49  The request is handled  {"requestID": "8a455c6d-ef60-4d2e-ac01-d95d5227fa93", "httpStatus": 404, "duration": "5.242µs"}

Desktop (please complete the following information):

  • OS: Debian Buster
  • Browser Firefox
  • Version 74.0

Where is 3000 located

Hello, I'm trying to run this using docker-compose, but I was failed because 3000 port is not found anywhere.
So where is the react app located, should I build my own?

go-ldap panic on arm devices

Describe the bug
werther uses go-ldap v2 which has problems running on my arm device giving error like
level":"info","ts":1586183401.5116482,"caller":"[email protected]/rlog.go:43","msg":"New request","requestID":"92f290e1-fe52-4a48-88ce-238ffeb71372","method":"POST","url":"/auth/login?login_challenge=c4d68ac1df8e41ad9436f5f23476f9d0"} 2020/04/06 14:30:01 ldap: recovered panic in processMessages: runtime error: invalid memory address or nil pointer dereference {"level":"info","ts":1586183401.5147185,"caller":"identp/identp.go:181","msg":"Failed to authenticate a login request via the OAuth2 provider","requestID":"92f290e1-fe52-4a48-88ce-238ffeb71372","error":"failed to login to a LDAP woth a service account: LDAP Result Code 200 \"Network Error\": ldap: response channel closed","errorVerbose":"LDAP Result Code 200 \"Network Error\": ldap: response channel closed\nfailed to login ...

To Reproduce
run current version of werther on an arm device and try to do an ldap auth request through Login page

Additional context
This is common issue caused by go-ldap v2 and can be fixed by updating werther to use go-ldap v3.

for reference similar issues are following
hashicorp/vault#6135
portainer/portainer#3244
go-ldap/ldap#195

Persist User Sessions

Is your feature request related to a problem? Please describe.
I am using Hydra/Werther with many different oauth2/oidc consumers, when users have to login to those consumers they have to put in their username/password individually for every consumer.

Describe the solution you'd like
I would like the werther login to persist user sessions for a configurable amount of time.

Describe alternatives you've considered
I've looked at and adjusted the various environment settings but have yet to figure out a fix to this issue

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.